I need to observe livedata changes in a Modelview to update a fragment (Adding the list to a recyclerview).
The implementation is working correctly but facing problems when switching between fragments.
If the implementation is on Fragment A when the user switches to Fragment B and then back to Fragment A a second livedata observer gets initiated. (Data in recyclerview gets duplicated) and so on...
I did some research on the Fragment Lifecycle and the need to remove the observer when moving between fragments (Either on Fragment Detach/Destroy) or before creating a new observer in the OnActivityCreated. But any of these worked.
I am observing the livedata as: mViewModel.getDetails().observe(getViewLifecycleOwner(), mObserver);
And tried to remove the observer as: mViewModel.getDetails().removeObservers(getViewLifecycleOwner()) or mViewModel.getDetails().removeObservers(this) or mViewModel.getDetails().removeObserver(mObserver) tried in OnViewCreated and onDestroyView and onDestory and onDetach
What is causing this and why removing the observer is not working?
FYI:
Here is the function i am using in the MainActivity to switch between fragments on navigation menu click
private boolean loadFragment(Fragment fragment) {
//switching fragment
if (fragment != null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit();
return true;
}
return false;
}
Try to set the value to null after
mViewModel.getDetails().removeObservers(getViewLifecycleOwner()).
mViewModel.getDetails().setValue(null)
Related
I have a Fragment (lets call it Base Fragment) containing clickable components. When one of these clickable components is pressed, a new Fragment appears over Base Fragment (lets call this Fragment A). When the back button is pressed from Fragment A, the view navigates back to Base Fragment.
To implement this, I have a FragmentContainerView called myFragmentLayout. When one of the clickable components is pressed, the following method gets called from Base Fragment to add the Fragment to the back stack:
private void addFragment(#NonNull Class<? extends Fragment> fragmentClass) {
mOnBackPressedCallback.setEnabled(true);
getChildFragmentManager().beginTransaction().setReorderingAllowed(true)
.add(R.id.myFragmentLayout, fragmentClass, null)
.addToBackStack(null)
.commit();
}
which enables the onBackPressedCallback in Base Fragment defined here to allow a back press to return to Base Fragment:
private final OnBackPressedCallback mOnBackPressedCallback =
new OnBackPressedCallback(false) {
#Override
public void handleOnBackPressed() {
if (getChildFragmentManager().getBackStackEntryCount() > 0) {
getChildFragmentManager().popBackStack();
} else {
setEnabled(false);
requireActivity().onBackPressed();
}
}
};
This code is achieving what I want it to achieve. However, if an orientation change happens inside Fragment A, then Base Fragment and Fragment A get recreated, but the back stack is lost. This causes a back press in Fragment A after a rotation to exit the app instead of returning to Base Fragment. How can I retain the back stack on an orientation change?
I am quite new in android, my intention is to pass this values to a fragment that it is my current fragment. But I want to update the fragment, I have this data in a different fragment.
When I execute this my bundle goes to the fragment. It doesn't change even I can do setText in my textView without the app stops.
private void passToScreen(String title, String artist, String album, Long duration) {
bundle.putString("songTitle",title);
bundle.putString("songArtist", artist);
bundle.putString("songAlbum", album);
bundle.putString("durationSong", duration.toString());
mActivity.getSupportFragmentManager()
.beginTransaction()
.detach(songScreen)
.commitNowAllowingStateLoss();
songScreen.setArguments(bundle);
mActivity.getSupportFragmentManager()
.beginTransaction()
.attach(songScreen)
.commitAllowingStateLoss();
})
What am I am doing wrong?
Thank you in advance.
If the fragment is already visible, then you can get the instance of that fragment using the FragmentManager:
MyFragment myFragment = (MyFragment) getSupportFragmentManager()
.findFragmentById(R.id.fragment_or_container_id);
myFragment.updateViews(bundle);
Then create the method in the fragment class to update the views:
public void updateViews(Bundle bundle) {
//update the views
}
You don't need to detach the fragment. Note that this will only work if the fragment is finished with the "commit" process. The commit() and commitAllowingStateLoss() functions work asynchronously, so if you call findFragmentById() immediately after commit(), it will return null.
Use interfaces to communicate between fragments
https://developer.android.com/training/basics/fragments/communicating.html
So, what my problem is that in one fragment(w/i a viewpager, I'll call this Fragment A) I click on this dynamically created button that adds a new fragment(I'll call this Fragment B) in a framelayout which allows me to use PayPal service. On PayPal Activity result, Fragment B communicates with the main Activity via a communicator(an interface class) to call Fragment A to change that text. But I'm getting a null pointer exeception crash.
To be specific:
what I did was that I made a global TextView variable that is initialized on click. I did this b/c I have a list of other things that are dynamically inflated and to avoid the TextView from being initialized with wrong layout I initialized it on click.
bidChange.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
eventListChangeKey = keyVal;
eventListChangeIdx = eventListIdx;
eventBiddingChangeIdx = finalI;
priceToChage = (TextView) biddersLayout.findViewById(R.id.single_list_bidder_bid_price);
Bundle bundle = new Bundle();
bundle.putInt("auctionID", auctionId);
bundle.putInt("dateID", dateId);
bundle.putInt("FromWhere", 2);
Fragment fragment = new Fragment_Home_ItemInfo_Bid();
fragment.setArguments(bundle);
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
fragmentManager.beginTransaction()
.add(R.id.container_mainScreen, fragment, "itemInfo_bid")
.addToBackStack(null)
.setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit();
}
});
In the main activity
public void changeBidderPrice(String s) {
Fragment fragment = viewPagerAdapter.getItem(1);
((Fragment_List) fragment).changePrice(s);
}
is what I do
back in Fragment A
public void changePrice(String val) {
priceToChage.setText(val);
dataMap.get(eventListChangeKey).get(eventListChangeIdx).getBidList().get(eventBiddingChangeIdx).setPrice(val);
}
I've thought this over an over but I just can't figure this out. I've searched for similar cases in StackOverflow but I wasn't able to get a help.
Would the problem be the way I initialize that TextView? or is it the way I'm calling Fragment A from the main activity?
for fragments onViewCreated() is called after onCreateView() and ensures that the fragment's root view is non-null. Any view setup should happen here. E.g., view lookups, attaching listeners.
source : codepath
for activities onCreate()
I have a MainActivity in my application, from where all the fragments are called using Navigation Drawer. And default fragment of the activity is 'A'. So everytime i open the application, 'A' fragment is called. when I hit 'back' from another fragment B, I want to get to default fragment 'A', as what happens in gmail - from any fragment if we hit back, it returns to default fragment "Primary mails".
I tried calling the 'A' fragment by adding the following code to the onPause() of fragment 'B'.
#Override
public void onPause() {
super.onPause();
fragment = new A();
FragmentTransaction fragTransaction = getFragmentManager().beginTransaction();
fragTransaction.replace(R.id.a,fragment ).commit();
}
But when I hit back, fragment 'A' is called for a moment, but then the application closes unexpectedly.
Logcat :
01-14 12:44:42.264: E/WindowManager(4655): android.view.WindowLeaked: Activity com.litchi.iguardian.MainActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView{41da6348 V.E..... R.....ID 0,0-513,243} that was originally added here
Whats the correct way of doing this ?
to back to previous fragment see below code :
fragmentManager.popBackStack(...);
put this in onBack event
to call popBackStack method you first need to call addToBackStack method while calling fragment
Use this code
android.support.v4.app.Fragment detail = new CurrentClass();
detail.setArguments(bundle);
android.support.v4.app.FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.add(R.id.content_frame, detail).addToBackStack("back")
.commit();
You should customize the behavior when you press the back button, because by default it will destroy launched activity and you see your fragment while callbacks goes from onPause() until onStop() and activity is not visible for you anymore. Just override this method, it must solve the problem:
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
// your code
return true;
}
return super.onKeyDown(keyCode, event);
}
But yeah, it is for global control, for switching fragments you may also call addToBackStack(); when you want return previous fragment by pressing back button.
instead of writing that code in onPause() you can write that code in onBackPressed() of your main activity .
I'm having a hard time understanding how to replace fragments using the FragmentPagerAdapter. I have a class that extends FragmentPagerAdapter (android.support.v13.app.FragmentPagerAdapter) and my MainActivity implements ActionBar.TabListener, in the FragmentPageAdapter class I use the getItem() to setup my 3 fragments. so far so good.
One of these fragments is a ListView (pos #1), which I use a Listener to check on the onItemClick().
I want to replace the current ListView Fragment with another fragment so what I did is that inside the onItemClick() I perform the following code:
FragmentManager mFragmentManager = mActivity.getFragmentManager();
//new fragment to replace
Fragment mFragment = new TabPlaceFragment();
mFragmentManager.beginTransaction()
.replace(R.id.pager, mFragment)
.addToBackStack(null)
.commit();
What this is doing is that the fragment is replace with a blank fragment, I'm assuming is the pager, but the mFragment does not get replace. I tried giving an Id to the Current fragment and tried replacing it.
mFragmentManager.beginTransaction()
.replace(R.id.frament_old, mFragment)
.addToBackStack(null)
.commit();
But what this does is that it overlaps the new fragment on top of the old fragment
finally I tried getting the Id of the fragment to replace with the new, but this also gave me a blank fragment.
//this gives me the current fragment I want to replace
Fragment mCurrent = mFragmentManager.findFragmentByTag(MainHelper.getFragmentName(mViewPager.getId(), 1));
mFragmentManager.beginTransaction()
.replace(mCurrent.getId(), mFragment)
.addToBackStack(null)
.commit();
I'm sure I'm not implementing the method correctly but I cannot find a good example to follow the logic of my code. I thought of putting the fragment replacement in the MainActivity under getItem(), but don't know how to call it from within my onItemClick() Listener.
Any help is highly appreciated!!!
Thanks.