The basic problem I have is i'm trying to get a refresh function to execute every time i return to a specific fragment.
So far I've been using the workaround of utilizing setUserVisibleHint() as shown below.
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
refresh();
}
}
However, this only works when switching between my main fragment and this fragment in question. What i'm wondering is how do i get this code to execute when i switch from one of my other tabs?
Help would be much appreciated.
Root cause: This is a feature from Android called offscreen page limit, it will retain the number of fragments to either side of current fragment, default value is 1. In your case using setUserVisibleHint is not enough to refresh data.
Solution:
public class YourFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container,Bundle savedInstanceState) {
if (getUserVisibleHint()) { // fragment is visible to users.
refresh();
}
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser && isResumed()) { // fragment is visible to users
refresh();
}
}
public void refresh(){
}
}
Fragments inside Pager act as visible even if there are not currently visible to user, the affected fragments are differ based on Pager offscreenPageLimit so trying to refresh fragment inside setUserVisibleHint or onResume has no effect when using Pager.
However that is fine as it is intended behavior, the aim of Pager is to let user switch between pages as you switch tabs in your browser..
If you still insist on refreshing pages you can do this inside the Pager addOnPageChangeListener like this:
viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){
#Override
public void onPageSelected(int position) {
super.onPageSelected(position);
Fragment fragment = getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":" + position);
if(fragment != null && fragment instanceof RefreshableFragment)
((RefreshableFragment) fragment).refresh();
}
});
Under the fragment, you should call your method under onResume() callback of fragment.
public void onResume(){
super.onResume();
.....
}
Related
I am working on an application where I am using bottom navigation on Home where i have three fragments on the second fragment called Post Ad I have a button called enter fragment zone through that i enter into another fragment now when I enter inside another fragment now I don't want there to show the bottom navigation so to hide it I am using a method inside my main activity called "setBottomNavigationVisibility" where I am writing code to set the visibility of bottom nav. but the problem is that it is throwing the null pointer exception in the mainactivty's method saying that
"void com.google.android.material.bottomnavigation.BottomNavigationView.setVisibility(int)' on a null object reference" on the method's line where i am setting the visibility
code of MainActivity
public class MainActivity extends AppCompatActivity {
NavController navController;
BottomNavigationView bottomNavigationView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
navController = Navigation.findNavController(this, R.id.fragmentContainerView);
bottomNavigationView = findViewById(R.id.activity_main_bottom_navigation_view);
NavigationUI.setupWithNavController(bottomNavigationView, navController);
}
public void setBottomNavigationVisibility(int visibility) {
bottomNavigationView.setVisibility(visibility);
}}
On the above method when i am trying to setting the visibility on the line bottomNavigationView.setVisibility(visibility); that's where it is thrwoing the exception
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentSecondBinding.inflate(inflater, container, false);
// Inflate the layout for this fragment
View view = binding.getRoot();
viewModel = new ViewModelProvider(requireActivity()).get(PageViewModel.class);
((MainActivity) requireActivity()).setBottomNavigationVisibility(View.GONE);
binding.toolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Navigation.findNavController(view).navigate(R.id.action_secondFragment2_to_postad);
}
});
No Please guide me how can i solve this error.
do something like this, define interface
interface DashboardActivityDelegate {
void hideBottomBar();
void showBottomBar();
}
your activity must implement this interface
and in your fragment in which you want to hide bottom nav view do this:
declare global variable
private DashboardActivityDelegate dashboardActivityDelegate;
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof DashboardActivityDelegate) {
dashboardActivityDelegate = (DashboardActivityDelegate)context;
}
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (dashboardActivityDelegate != null) {
dashboardActivityDelegate.hideBottomBar();
}
}
#Override
public void onDestroy() {
super.onDestroy();
if (dashboardActivityDelegate != null) {
dashboardActivityDelegate.showBottomBar();
}
}
in fragment you want hide bottom bar, just call this line in onViewCreated
getActivity().findViewById(R.id.activity_main_bottom_navigation_view).setVisibility(View.GONE);
and when you leave fragment, call this line in onDestroy or onStop to visible bottom nav for another fragment:
getActivity().findViewById(R.id.activity_main_bottom_navigation_view).setVisibility(View.VISIBLE)
I have a HostActivity that uses ViewPager and TabLayout to switch between multiple Fragments. When I switch between the tabs, the Fragments instance does get the updated data. I also see the updated data in onCreateView of the Fragment instance, but the TextView.setText does not get updated. When I check the visibility of Fragment, it always shows Invisible. How do I make the fragment visible when I switch tabs so that the view gets updated with new data? Is there something missing in the Fragment/Activity Lifecycle? I am implementing ViewPager for the first time so it will be helpful to know if I am missing something.
Fragment Class:
public class StepFragment extends Fragment { #Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (getArguments() != null) {
step = getArguments().getParcelable(SELECTED_STEP);
mDescription = step.getDescription();
}
View view = inflater.inflate(R.layout.step_fragment, container, false);
ButterKnife.bind(this,view);
Log.e(TAG, "onCreateView: "+mDescription); **// THIS GETS UPDATED DATA**
tvStepDescription.setText(mDescription);
}
return view;
}
}
Here is my Host Activity:
public class StepActivity extends AppCompatActivity {
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_step);
fragmentSelectAdapter = new StepFragmentSelectAdapter(getSupportFragmentManager(),this,steps,recipe);
mViewPager.setAdapter(fragmentSelectAdapter);
mTabLayout.setupWithViewPager(mViewPager);
stepFragment = (StepFragment) getSupportFragmentManager().findFragmentById(R.id.step_container);
if(stepFragment == null) {
stepFragment = StepFragment.newInstance(step, recipe);
stepFragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.add(R.id.step_container, stepFragment)
.commit();
} else {
stepFragment = StepFragment.newInstance(step, recipe);
stepFragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.replace(R.id.step_container, stepFragment)
.commit();
}
}
}
Here is my FragmentPagerAdapter, which seems to be getting the correct data as per the tab position in getItem method:
public class StepFragmentSelectAdapter extends FragmentPagerAdapter {
...
#Override
public Fragment getItem(int position) {
**// THIS GETS UPDATED DATA**
Log.e(TAG, "getItem: \nDecr: "+steps.get(position).getDescription()+"\nVideo: "+steps.get(position).getVideoURL()+"\nImage: "+steps.get(position).getThumbnailURL());
return StepFragment.newInstance(steps.get(position),recipe);
}
#Override
public int getCount() {
if (steps == null){
return 0;
}
return steps.size();
}
...
}
As far as I could understand about the problem that you are having there, I think you should implement an onResume function in your StepFragment which will get the updated data from some source and will display this in the TextView. However, I can think of a potential problem in your StepFragmentSelectAdapter. You are creating a new instance each time you are switching the tabs.
You should have the Fragment instances created before and if you are about to pass the data among fragments, you might consider having a BroadcasReceiver or listener function by implementing an interface.
So the PagerAdapter should look something like this.
public class StepFragmentSelectAdapter extends FragmentPagerAdapter {
public ArrayList<StepFragment> stepFragments;
public StepFragmentSelectAdapter(ArrayList<Step> steps) {
stepFragments = new ArrayList<>();
for(int i = 0; i < steps.size(); i++) {
stepFragments.add(StepFragment.newInstance(steps.get(position),recipe));
}
}
...
#Override
public Fragment getItem(int position) {
**// THIS GETS UPDATED DATA**
Log.e(TAG, "getItem: \nDecr: "+steps.get(position).getDescription()+"\nVideo: "+steps.get(position).getVideoURL()+"\nImage: "+steps.get(position).getThumbnailURL());
return stepFragments.get(position);
}
#Override
public int getCount() {
if (steps == null){
return 0;
}
return steps.size();
}
...
}
Thanks for the hint. I got around this problem by replacing ActionBar with a custom ToolBar with back ImageButton and using click listener to get back to the calling activity.
backButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
getActivity().onBackPressed();
}
});
I have a main Activity in which I created a ViewPager that instantiate 3 other Fragments. One of these is a GridView which makes a popup appear when the user click on one item. Then, in this popup, I have a simple button.
What I want to do is: when the user click on this button, I would like to access a method in my main Activity (that should change the current item of my ViewPager) and then dismiss the popup.
I tried everything I could, but I cannot achieve this... I can set up the click event on my popup and dismiss it easily, but I didn't find out how I can access a method (or even a variable) from my popup to my main Activity.
I will put my most relevant code in here so you can understand the structure of my classes (hopefully...).
My main Activity:
public class MainActivity extends FirstActivity{
private ViewPager mViewPager;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewPager = (ViewPager) findViewById(R.id.viewpager);
// Set an Adapter on the ViewPager
mViewPager.setAdapter(new MainActivity_Adapter(getSupportFragmentManager()));
public void onSaveInstanceState(Bundle savedInstanceState)
{
menuBar.onSaveInstanceState(savedInstanceState);
}}
My ViewPager activity:
public class MainActivity_Adapter extends FragmentPagerAdapter{
public MainActivity_Adapter(FragmentManager fm)
{
super(fm);
}
#Override
public Fragment getItem(int position)
{
// Set the color background for each page
switch (position)
{
case 0:
return MainActivity_Inventory.newInstance();
case 1:
return MainActivity_Map.newInstance();
default:
return MainActivity_AR.newInstance();
}
}
// The number of Splash Screens to display
#Override
public int getCount()
{
return 3;
}}
My "Inventory" Fragment
public class MainActivity_Inventory extends Fragment implements View.OnClickListener{
public static MainActivity_Inventory newInstance()
{
MainActivity_Inventory frag = new MainActivity_Inventory();
Bundle b = new Bundle();
frag.setArguments(b);
return frag;
}
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Select the layout
int layout;
layout = R.layout.activity_inventory_01;
// Inflate the layout resource file
View view = getActivity().getLayoutInflater().inflate(layout, container, false);
// Set the grid view
GridView gridview = (GridView) view.findViewById(R.id.inventory_gridView);
gridview.setAdapter(new InventoryImageAdapter(super.getActivity()));
gridview.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
public void onItemClick(AdapterView parent, View v, int position, long id)
{
// Create a popup to show item details
createPopup();
}
});
return view;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
}
public void createPopup()
{
DialogFragment newFragment = new PopupActivity_Inventory();
newFragment.show(getFragmentManager(), "itemDetails");
}
#Override
public void onClick(View v)
{
}}
And my popup dialog fragment:
public class PopupActivity_Inventory extends DialogFragment{
#Override
public Dialog onCreateDialog(final Bundle savedInstanceState)
{
// Build the alert dialog
final Dialog dialog = new Dialog(this.getActivity());
// Get the layout inflater
final LayoutInflater inflater = getActivity().getLayoutInflater();
// Set up the dialog box
dialog.setContentView(inflater.inflate(R.layout.activity_inventory_popup_01, null));
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
dialog.getWindow().setGravity(Gravity.TOP);
//dialog.getWindow().getAttributes().y = 100;
(dialog.findViewById(R.id.brick_button_01)).setOnClickListener(new View.OnClickListener()
{
public void onClick(View view)
{
// When button is clicked, ACCESS MAIN ACTIVITY!
dialog.dismiss();
}
});
return dialog;
}}
I really hope you can help me with this... I really need to get it working. Thank you very much!
If you need further details or explanation, please just tell me.
The best thing to do is use EventBus library. I have a demo app in which you can add items to RecyclerView from anywhere within the app using EventBus. You can use it as a reference to simply do something else instead of current task. Here is the link to the repo:
https://github.com/code-crusher/android-demos/tree/master/EventBusDemo
And if you want to understand how it works you can refer to my article, it explains how to make communications like this easy:
https://medium.com/#code_crusher/eventbus-for-android
Hope it helps. Happy coding :)
Read this https://developer.android.com/training/basics/fragments/communicating.html
Look for "To allow a Fragment to communicate up to its Activity, you can define an interface in the Fragment class and implement it within the Activity...."
Another way to achieve it is using an EventBus and post events by Fragments to be caught by Activities.
I'm new in Android App developing via Java. I'm using Eclipse. If I create an Activity, Eclipse automatically generates a Placeholderfragment Class and Fragment.xml. Can I disable this function? Or is it not advisable to do that? I delete those files because I find it more complicated to use than just write in one xml file at the moment.
Second question is how do I implement a "starting Page" for my App? For example some sort of a logopage which automatically disables after a few seconds and switches to a new activity. Create a separate Activity for it or do I use something else?
Actually you need two activities, one startup Activity which is used to show your logo or some guide,the other is a MainActivity which should be started by the startUp Activity.
In short You can do something like this:
public class MainActivity extends FragmentActivity {
Fragment fragment;
String className;
#Override
protected void onCreate(Bundle savedInstanceState) {
Log.d("MainActivity", "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Store the name of the class
className=MainActivity.class.getSimpleName();
//First fragment should be mounted on oncreate of main activity
if (savedInstanceState == null) {
/*fragment=FragmentOne.newInstance();
getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment).addToBackStack(className).commit();
*/
Fragment newFragment = FragmentOne.newInstance();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.container, newFragment).addToBackStack(null).commit();
Log.d("FRAGMENT-A", "fragment added to backstack");
}
}
}
FragmentOne.java
public class FragmentOne extends Fragment{
String className;
public static FragmentOne newInstance(){
Log.d("FragmentOne", "newInstance");
FragmentOne fragment = new FragmentOne();
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d("FragmentOne", "onCreateView");
View view=inflater.inflate(R.layout.fragment_one, container, false);
//Store the name of the class
className=FragmentOne.class.getSimpleName();
return view;
}
}
Let me know if you need any more info
Well, in a Single Activity setup, the way I did this was the following:
public class SplashFragment extends Fragment implements View.OnClickListener
{
private volatile boolean showSplash = true;
private ReplaceWith activity_replaceWith;
private Button splashButton;
public SplashFragment()
{
super();
}
#Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
try
{
activity_replaceWith = (ReplaceWith) activity;
}
catch (ClassCastException e)
{
Log.e(getClass().getSimpleName(), "Activity of " + getClass().getSimpleName() + "must implement ReplaceWith interface!", e);
throw e;
}
startSwitcherThread();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.fragment_splash, container, false);
splashButton = (Button) rootView.findViewById(R.id.fragment_splash_button);
splashButton.setOnClickListener(this);
return rootView;
}
public void startSwitcherThread()
{
Thread splashDelay = new Thread()
{
#Override
public void run()
{
try
{
long millis = 0;
while (showSplash && millis < 4000)
{
sleep(100);
millis += 100;
}
showSplash = false;
switchToFirstScreen();
}
catch (Exception e)
{
e.printStackTrace();
}
}
};
splashDelay.start();
}
private void switchToFirstScreen()
{
activity_replaceWith.replaceWith(new FirstFragment());
}
#Override
public void onClick(View v)
{
if(v == splashButton)
{
if(showSplash == false)
{
switchToFirstScreen();
}
}
};
}
Where the ReplaceWith interface is the following:
import android.support.v4.app.Fragment;
public interface ReplaceWith
{
public void replaceWith(Fragment fragment);
}
And the replace function is implemented like so:
#Override
public void replaceWith(Fragment fragment)
{
getSupportFragmentManager()
.beginTransaction().replace(R.id.fragment_container, fragment)
.addToBackStack(null)
.commit();
}
Now, most people will say this is not a good approach if you're using multiple activities, and/or using multiple orientations and aren't just simply displaying a single Fragment in a single Activity no matter what. And they are completely right in saying so.
Multiple orientations would require the Activity to be responsible for knowing what is the "next" Fragment at a given replace call, and where to place it (which container, or to start it in a new Activity). So this is a valid approach only if you are certain that you only have one container and there is one Fragment shown at a given time.
So basically, if this does not apply to you, then you need to utilize the same approach (make a specific delay before you replace the current Fragment or Activity with another one, this specific code allows you that once the splash has been shown once, then clicking the button will automatically take you to the next screen - typical game splash setup, really), but use activity callbacks specific to the Fragment in order to swap one out for the other.
A Fragment setup I recommend and isn't relying on this special case can be seen here: Simple Android Project or its equivalent on Code Review: Fragment Start-Up Project
This is my situation:
I have several fragments added dynamicly to an FragmentStatePagerAdapter, this works fine. But now i want to be able to replace an fragment when I push on an button.
public class QuestionFragment extends UpperFragment {
...
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setRetainInstance(true);
CustomViewPager.enabled = true;
View rootView = inflater.inflate(R.layout.question, container, false);
Button btn = ((Button) rootView.findViewById(R.id.bQuestion));
if (how == true) {
btn.setVisibility(View.VISIBLE);
btn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// lijst afgaan met alle how en kijken welke id nodig is
for (int i = 0; i < XmlParserSax.howFragments.size(); i++) {
Fragment how = XmlParserSax.howFragments.get(i);
if (howId.equals(((UpperFragment) how).getIdNum())) {
FragmentTransaction transaction = getFragmentManager()
.beginTransaction();
transaction
.replace(R.id.flQuestion, how, "howFragment")
.setTransition(
FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.addToBackStack(null).commit();
break;
}
}
}
});
} else {
btn.setVisibility(View.GONE);
}
return rootView;
}
So when i press the button the current layout (R.id.flQuestion) is replaced with the new fragment. This works, but the tricky part comes here:
When I slide to the next fragment and the slide to the fragment with the button it keeps working but if i slide 2 times to the next fragment (of the same type QuestionFragment) it does the functionallity of the new fragment but it doesn't show the new fragment.. So it seems that it can't replace the R.id.flQuestion because it is stored in memory maybe?
I need to be sure that the fragment is always replaced even if the next 2 fragments are of the same type and same layout (R.id.flQuestion)..
This is the class layout of the new frag
public class HowFragment extends UpperFragment {
..
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setRetainInstance(true);
View rootView = inflater.inflate(R.layout.how, container, false);
//if back key pressed return to layout of Question
rootView.setFocusableInTouchMode(true); //this line is important
rootView.requestFocus();
rootView.setOnKeyListener( new View.OnKeyListener()
{
#Override
public boolean onKey( View v, int keyCode, KeyEvent event )
{
if( keyCode == KeyEvent.KEYCODE_BACK )
{
CustomViewPager.enabled = true;
return false;
}
return true;
}
} );
//don't allow pushing button again
rootView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
return true;
}
});
return rootView;
}
Also important to tell: i'm using framelayouts for both the fragments (so no hard coded fragment tag in the xml)
To make it clear:
This happens when the next fragment is from different class, no problem
This happens when next fragments are from the same layout and class:
I'll have to test the code, but from what I see, easiest way to make this work is to grab new Fragment even if it is the same fragment class.
Fragment how = XmlParserSax.howFragments.get(i);
If this function is returning new instance of a fragment, it should work.
Hope that helps
Edit :
I'm pretty sure the activity can access the button after the fragments are created.
Otherwise you need a handler to pass the click to handle it in the adapter. I'm seeing the your list of fragments are static (Not recommended). Since you haven't added any codes for how you setup the pageradapter, I have no idea what list you are using, but you need to change out the item in that list. From the Activity where you initialized the pager, you can call the public function to replace the current pager item pager. (you can use ViewPager.getCurrentItem())
I haven't tested, so you might have to tweak and play around to perfect it.
Hope this helps.
Here is a sample :
public static class MyAdapter extends FragmentStatePagerAdapter {
ArrayList<Fragment> fragmentArray = new ArrayList<Fragment();
public MyAdapter(FragmentManager fm) {
super(fm);
}
//didn't put in the function to populate the list with fragments
public void replaceItem(Fragment newFrag, int pos){
fragmentArray.remove(pos);
fragmentArray.add(pos,newFrag);
notifyDataSetChanged();
}
#Override
public int getCount() {
return fragmentArray.size();
}
#Override
public Fragment getItem(int position) {
return fragmentArray.get(position);
}
}