Persisting fragment state - java

I have a frame layout in an activity to which i want to display different fragments inside. I have a sliding drawer with 3 options, each of which lead to a fragment being loaded inside the frame layout. Currently i use the following to accomplish this:
Fragment nextFragment = determineFragmentToSwitchTo(nextFragmentTag);
transaction.replace(R.id.fragment_container, nextFragment);
The first method determines what fragment i need by evaluating the nextFragmentTag string and loading a new fragment like so:
if (fragmentTag.equals(Constants.STUDENTPAGE))
nextFragment = new StudentFragment();
else if (fragmentTag.equals(Constants.TEACHERPAGE))
nextFragment = new TeacherFragment();
else if (fragmentTag.equals(Constants.PARENTPAGE))
nextFragment = new ParentFragment();
Clearly this approach is creating a new fragment each time and running through the whole fragment lifecycle without saving state. So if i am on the student page and scrolling through the student list and i switch to the parent page, when i go back to the student page, it reloads the entire list (i am fetching it from a server) and looses my place in it. How can i get it to persist state and sort of cache that fragment in the manager (if that makes sense)?

You could use the FragmentTransaction's hide(Fragment) and show(Fragment) methods, e.g.:
// In the parent Activity
StudentFragment studentFragment;
TeacherFragment teacherFragment;
ParentFragment parentFragment;
Fragment fragmentOnDisplay;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Initialize fragmentManager, fragmentTransaction, etc.
studentFragment = (StudentFragment) fragmentManager.findFragmentByTag(Constants.STUDENTPAGE);
if (studentFragment == null) {
studentFragment = new StudentFragment ();
fragmentTransaction.add(R.id.your_frame_layout, studentFragment, Constants.STUDENTPAGE);
}
// repeat the same procedure for the other two fragments
// Suppose you want to begin with the teacherFragment on
// display - in that case hide the studentFragment and
// the parentFragment:
fragmentTransaction.hide(studentFragment);
fragmentTransaction.hide(parentFragment);
fragmentOnDisplay = teacherFragment;
fragmentTransaction.commit();
}
Now whenever you need to switch your fragments, simply hide the fragment on display, and show the fragment you need, e.g.:
...
fragmentTransaction.hide(fragmentOnDisplay);
fragmentTransaction.show(parentFragment);
fragmentOnDisplay = parentFragment;
fragmentTransaction.commit();

Related

Missing TextView data(text) in a fragment

I have a few edittexts and textviews in Fragment A. I use edittexts to get value from user and then I calculate based on my formula and show the result in the textview.
For example Edittext A has 1 and Edittext B has 2 and my formula is add so the textview will show the result as 3.
Also, there is a button, info on the Fragment A. On clicking this button a new fragment B is displayed using the below code:
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.contentFrag, fragment);
fragmentTransaction.addToBackStack(backStackStateName);
fragmentTransaction.commit();
Now, when I press the back button, I let the system call super.onBackPressed() so that the current Fragment B is removed and I get back Fragment A.
MY PROBLEM:
Now the edittexts retain all their values, however the value I calculated and displayed on TextViews disappears.
I checked onViewStateRestored(Bundle savedInstanceState) method however the parameter savedInstanceState is null.
My Question:
How do I save/retrieve the values for my textviews?
OR
where can I call that code again that calculates the data for textviews ??
To resolve this problem you need use some data layer like Shared Preference or SQL storage. In some primitive way it looks like:
public override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater?.inflate(R.layout.fragment_blank, container, false)
//update you UI from local STORAGE on create
textView.text = sharedPreference.getInt("result_data", 0).toString()//make request local for date
//calling your calculation
button.setOnClickListener {
calculateData(dataInput, object : Callback {
override fun onCalculated() {
//update you UI from local STORAGE layer in MAIN THREAD
Handler(Looper.getMainLooper()).post {
textView.text = sharedPreference.getInt(
"result_data",0
).toString()
}
}
})
}
}
//you calculate and request to data layer
private fun calculateData(inPutData:Int, callback:Callback){
//use background for calculate It will be available.
Thread{
//calculate
val result = inPutData+ ARG_PARAM
//save result in LOCAL STORAGE
sharedPreference.edit().putInt( " result", result).apply()
//update UI
callback.onCalculated()
}.start()
}
interface Callback{
fun onCalculated()
}
Think also about MVP and Presenter. It is also be available.
In open source you can find amazing MVP library called moxy. It can restore your view state.
Using SharedPreferences will solve your issue just save them in sharedPreferences and clear SharedPreference after Use.

Which method is being called after popBackStack?

I have an activity where I am calling three fragments - each depending on each other:
A(ctivity) -> f1 (Fragment one, title {is|should}: list) -> f2 (Fragment two, title {is|should}: overview) -> f3 (Fragment three, title {is|should}: detail)
ATM I use the following method call to jump backwards:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount()>0){
fragmentManager.popBackStack();
}
}
}
This works fine.
I am overriding the ActionBar title in each fragment like this:
ActionBar bar = getSherlockActivity().getSupportActionBar();
bar.setTitle(R.string.title_f3);
When navigating forward (like shown above) this works flawlessly but navigating backwards the title of the ActionBar isn´t updated:
f3 (title {is|should}: detail) -> f2 (title {is}: detail, {should}: overview) -> f1 (title {is}: detail, {should}: list)
Obviously I could just update it again when the fragment is shown. But my debugger never stops in any of the methods I´d except which would be called like onResume().
So is there actually any method being called in a previous fragment after popBackStack() ?
I know this is a bit late for an answer but for anyone who navigates here this might help!
First thing is first: popBackStack()doesn't pop a fragment, it pops a fragment transaction. So the last fragment transaction is reversed upon being called. If you were displaying FragmentA currently and your transaction was:
fragmentTransaction.replace(R.id.your_layout, fragmentB);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
It would replace FragmentA with FragmentB, and add that transaction (not the fragment) to the back stack. If you then hit the back button, it pops the transaction off the back stack, which was "replace this FragmentA with a FragmentB". Essentially, this instruction reverses the last transaction and removes it from the stack of transactions carried out. If the original FragmentA still exists, it uses that one. If it's been destroyed, it makes a new one.
So, if the Fragment hasn't been destroyed, then recalling the fragment after using on popBackStack(), the onStart() and onResume() methods are called. If the Fragment has been destroyed previously, then the lifecycle methods will be called starting from onAttach(). It's the same as pressing the back button on Activities.
Now the important bit, what happens re fragment lifecycle when we pop off back stack? Well as said before the fragment transaction is reversed so:
Scenario 1: Your fragmentB didn't already exist before transaction.
In this case the onCreate() and onAttach() methods are called during the transaction so the fragment will be destroyed and detached if you call popBackStack() and reverse the transaction (Note FragmentA probably already existed so replacing it wont destroy it as we're not undoing a fragment creation). In this case the lifecycle methods will be called starting from onAttach().
Scenario 2: Your fragmentB did already exist before transaction. In this case the fragment won't be destroyed and the next time you access it the onStart() and onResume() methods are called.
This fellow here explains a few things about using popbackstack() http://vinsol.com/blog/2014/09/19/transaction-backstack-and-its-management/ and the fragment lifecycle http://vinsol.com/blog/2014/10/22/fragment-view-state-retention-a-dirty-solution/. The other related posts are worth reading too!
use addOnBackStackChangedListener method in your BaseActivity, which will be called any time backstack changes
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
FragmentManager fm = getSupportFragmentManager();
if (fm != null) {
int backStackCount = fm.getBackStackEntryCount();
if (backStackCount == 0) {
if (getSupportActionBar() != null) {
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_menu);
}
setToolbarTittle(R.string.app_name);
} else {
if (getSupportActionBar() != null) {
getSupportActionBar().setHomeAsUpIndicator(R.drawable.back);
}
}
}
}
});
My workaround is to get the current title of the actionbar in the Fragment before setting it to the new title. This way, once the Fragment is popped, I can change back to that title.
#Override
public void onResume() {
super.onResume();
// Get/Backup current title
mTitle = ((ActionBarActivity) getActivity()).getSupportActionBar()
.getTitle();
// Set new title
((ActionBarActivity) getActivity()).getSupportActionBar()
.setTitle(R.string.this_fragment_title);
}
#Override
public void onDestroy() {
// Set title back
((ActionBarActivity) getActivity()).getSupportActionBar()
.setTitle(mTitle);
super.onDestroy();
}
There's also one other good source that you can read through.
I think, the important point is the difference of the transaction you performed on Fragment B. If it's add, then no lifecycle methods are called on Fragment A, if it's replace, you will get some calls on those methods.
I use following workaround:
1) set 1st fragment setHasOptionsMenu(false) before add 2nd fragment on top of 1st one.
2) set 1st fragment setHasOptionsMenu(true) in onOptionsItemSelected() after return from 2nd fragment.
3) onCreateOptionsMenu() of 1st fragment should be called and you can change actionbar here.
But I want to know a better solution.
You can find a nice Fragment lifecycle diagram in the android docs here. So yes, if the fragment is brought to the foreground from the backstack onCreateView(), onActivityCreated(), onStart() and onResume() are called.

Android CardFlip Animation not working

I am experimenting with some Animations with Android. Basically I have this Activity that displays data from an API. Now when the user wishes to refresh that data, the Front card so to speak of which contains the data, flips to the Back card which is a loading animation. The process of loading new data kicks off and when it finishes the method that flips the so to speak of cards is called so the data can be revealed.
With my intentions in place I have everything explained above 99% complete. When I have the data from the API back in the UI thread controlling the cards, I am ready to update those interface objects that are part of the front card.
But setting the text of a TextView no longer works, and I'll explain that. When the front card gets switched out, the back card comes to the foreground, but when it comes time for the front card to come back with new data, it comes back with its default XML values. I can set TextViews all day of this front card but it return blank.
Code is below, please keep in mind I like to reverse engineer things like this. So some code I do not understand, it the sample from the Google Android development guide.
Here I set up some kind of fragment manager in the onCreate method of the Activity.
mShowingBack is a boolean for if the back card is the card in the foreground.
if (savedInstanceState == null) {
// If there is no saved instance state, add a fragment representing the
// front of the card to this activity. If there is saved instance state,
// this fragment will have already been added to the activity.
getFragmentManager()
.beginTransaction()
.add(R.id.container, new CardFrontFragment())
.commit();
} else {
mShowingBack = (getFragmentManager().getBackStackEntryCount() > 0);
}
// Monitor back stack changes to ensure the action bar shows the appropriate
// button (either "photo" or "info").
getFragmentManager().addOnBackStackChangedListener(this);
Even if I do not update the textviews with the new data, the front card is still bare and empty. Below is the method that flips the the two cards:
public void onClickFlipCard(View view) {
if (mShowingBack) {
getFragmentManager().popBackStack();
return;
}
// Flip to the back.
mShowingBack = true;
// Create and commit a new fragment transaction that adds the fragment for the back of
// the card, uses custom animations, and is part of the fragment manager's back stack.
getFragmentManager()
.beginTransaction()
// Replace the default fragment animations with animator resources representing
// rotations when switching to the back of the card, as well as animator
// resources representing rotations when flipping back to the front (e.g. when
// the system Back button is pressed).
.setCustomAnimations(
R.animator.card_flip_right_in, R.animator.card_flip_right_out,
R.animator.card_flip_left_in, R.animator.card_flip_left_out)
// Replace any fragments currently in the container view with a fragment
// representing the next page (indicated by the just-incremented currentPage
// variable).
.replace(R.id.container, new CardBackFragment())
// Add this transaction to the back stack, allowing users to press Back
// to get to the front of the card.
.addToBackStack(null)
// Commit the transaction.
.commit();
}
When that method is called, even though it is not included the method call for retrieving the new data kicks off. I am not for sure if like creates a new instance of the card layout so when I try to modify it, I am not actually in touch with it or something?
When I get the data for example all I do is use the textview object used before the data load to set the new data:
textview_example.setText("Example, but nothing appears...")
Even further, when I call the textviews getText method, it returns text but none is shown...Any help would be great. If it matters here are the two fragment classes that represent the two cards,(The front where data is displayed and the back where a loading animation is shown).
Front Card Fragment:
public class CardFrontFragment extends Fragment {
public CardFrontFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_card_front, container, false);
}
}
Back Card Fragment
public class CardBackFragment extends Fragment {
public CardBackFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_card_back, container, false);
}
}
Here is also a link to the Android guide that I follow...
Animation works but afterward as I said I can not edit any of the properties of interface objects. Thanks!
Link: http://developer.android.com/training/animation/cardflip.html

Android Trying to display a welcome fragment when app starts, but not after

I've been looking for a way to have the blank detail side of my fragment layout host a welcome screen (or something - login perhaps) on start up. Afterwards, when a user presses one of the left side menu items, I'd like to eliminate the fragment for the remainder of the program run. I don't want to add it to the backstack, as that messes up my configuration changes. I've considered using shared prefs to host a boolean about whether the fragment has been displayed. The only concern with this method is where to safely reset the boolean value for the next run of the app. I'm of the impression that there's no gaurantee that the onStop, onDetach etc. will definitely get called upon closing of the app, so if the app got closed in the wrong state, it would be rendered useless ( the first fragment wouldn't display - crash )
Anyone have any ideas on how I could implement a filler for the right side of the app upon startup?
I've been trying to add something to the onCreate of my main activity thus far with no success.
Thanks in advance.
Ken
If your fragment can be part of its own Activity, you can use the android:noHistory="true" attribute to keep the Activity off of the backstack. If your user tries to navigate backwards, it'll hit the bottom of the backstack twice before exiting your application.
If you can't split your fragment into its own activity, noHistory may not work -- I can't say as I haven't tried it myself.
I was able to come up with a solution to creating a welcome or login screen which will display both fragments and activities from the main activity. Seems to be working fine as tested.
private boolean welcomeShown;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_list);
if (findViewById(R.id.item_detail_container) != null) {
mTwoPane = true;
((MainFragment) getSupportFragmentManager().findFragmentById(
R.id.item_list)).setActivateOnItemClick(true);
}
if (savedInstanceState != null){
welcomeShown = savedInstanceState.getBoolean("displayed");
}
if(!welcomeShown){
if (mTwoPane){
WelcomeFragment welcomeFragment = new WelcomeFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.item_detail_container, welcomeFragment)
.commit();
}
else{
Intent welcomeIntent = new Intent(this, WelcomeActivity.class);
startActivity(welcomeIntent);
welcomeShown = true;
}
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("displayed", true);
}

How to pass data from an activity to a fragment inside a navigation drawer android

A navigation drawer with 3 Fragments, the third Fragment has a TextView with an on Click listener. Once it has been clicked a layout activity will open on the top which includes a ListView to allow the user to select/click on a specific Item, so later on this selected item info should be displayed on that TextView within the third fragment.
is there any method to pass data because I have used a class to pass data but the TextView wouldn't be refreshed with the sent data
This works as a design pattern to share arguments between the Activity and third fragment
--------------------------DataHolder Class---------------------------------
public class DataHolder {
private static DataHolder dataHolder = null;
private DataHolder() {
}
public static DataHolder getInstance() {
if (dataHolder == null)
{dataHolder = new DataHolder(); }
return dataHolder;
}
private String item;
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
}
If you find using startActivityForResult not sufficient in your case or using EventBus, you can overcome this by using SharedPreferences and Fragment/Activity lifecycle.
So once you start new Activity first Activity will go in onPause and with it all its Fragments will be put in onPause. When user clicks on one of the ListView items in your second Activity you can store the value inside SharedPreferences like:
PreferenceManager.getDefaultSharedPreferences(SecondActivity.this)
.edit().putString(key, selectedItemInfoHere).apply();
Then override inside your first Activity and in your third Fragment method onResume() and inside just make checking:
#Override
public void onResume() {
super.onResume();
String value = PreferenceManager.getDefaultSharedPreferences(getContext())
.getString(key, "");
if (value != null && !value.isEmpty()) {
//You have selected item value update TextView
}
}
Note that once you don't need this value you will need to remove it, because it will update your TextView every time when onResume is called. To remove value just call:
PreferenceManager.getDefaultSharedPreferences(getContext()).edit().remove(key);
If I understood you correctly, you have flow 3rd fragment --> activity which should update fragment which launched it. In this case, as for me, the most clean solution is from your opened activity call startActivityForResult method to call activity-host of your fragments and handle all what you need in overridden onActivityResult method. Than just call your fragment's updateTextView() or something like that.
On the other hand you can use this library to send messages between components, but you should be careful with usage and think about corner cases related to components lifecycle.
So, choose solution according your needs:)

Categories