how to store the state of fragment with navigation bottom menu - java

I'm trying to store the value of my fragments components inside an activity with lower navigation menu, controlled by navController.
My problem is that when I change one fragment to another (onPause) with the bottom navigation, or rotate the screen, all my data in the component layout is lost.
is there a solution to solve this problem?

Store the data in a Viewmodel and observe it from the UI. The stored data is not lost if the framework destroys and re-creates the activities and fragments during a configuration change or other events.
https://developer.android.com/topic/libraries/architecture/viewmodel?hl=en
Create class that extends ViewModel and store your variable in it as LiveData like in this example:
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<User>>();
loadUsers();
}
return users;
}
private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
Then you can access the viewmodel from the activity and fragments like this.
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
For further details check the documentation on the link above.

Related

Interface communication between two Fragments

I have implemented a master-detail view. With two fragments being displayed side by side on a large 10-inch screen. Fragment A Displays a list of orders. When an order is selected the details of that order are displayed in Fragment B. In fragments B after processing the order items. I want to notify Fragment A to update the UI and colour the processed order in the list of orders.
The current method that I have tried was creating an interface in Fragment B implementing the interface in Fragment A. However, this method does not seem to work as when I try and set the instance of the interface in the onAttach method the application crashes as the context is still the context of Fragment A.
#Override
public void onAttach(#NonNull Context context)
{
super.onAttach(context);
if (context instanceof OnStockAddedListener)
{
onStockAddedListener = (OnStockAddedListener) this.getActivity();
} else
{
throw new ClassCastException(context.toString());
}
}
How can i go about doing this.
Your fragments are hosted in an Activity, and that activity is what's passed to onAttach(). So your activity needs to be responsible for dispatching communication between your fragments.
So, in FragmentB, you cast your Activity to your listener interface when you're attached:
#Override
public void onAttach(Context context) {
super.onAttach(context);
this.onStockAddedListener = (OnStockAddedListener) context;
}
And you implement the interface in your Activity:
public class MyActivity implements OnStockAddedListener {
#Override
public void onStockAdded(Stock stock) {
FragmentA fragmentA = (FragmentA) getSupportFragmentManager()
.findFragmentByTag(/* insert tag here */);
fragmentA.handleStockAdded(stock);
}
}
And you receive these messages in FragmentA:
public class FragmentA {
public void handleStockAdded(Stock stock) {
// update ui, or whatever else you need
}
}
The main thing is to not think about FragmentA talking to FragmentB, or FragmentB talking to FragmentA. Instead, FragmentA and FragmentB both talk to the Activity, and the Activity can talk (as required) with either FragmentA or FragmentB. Everything flows through the activity.

How to get/set data for each programmatically generated fragment

I have question about programmatically generated fragments in the android.
Now I'm creating app that helps in training on the gym. I want to create something like "live training" - that means user will have some exercises, each exercise will have weight to lift etc.
I was thinking that each exercise would be a fragment (and have own data), but now I think about how to communicate to autogenerated fragments from parent activity to e.g read some data.
The user should be able to switch between exercises (so any series and weight should be saved for each exercise
until the workout is finished), but now i'm looking for best solution for that and I'm stuck :/. I've never done such a thing before.
Use Model View View Model Architecture with live data for observing data changes.
Architecture Components provides ViewModel helper class for the UI controller that is responsible for preparing data for the UI. ViewModel objects are automatically retained during configuration changes so that data they hold is immediately available to the next activity or fragment instance.
View Model
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
Master Fragment
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
Details Fragment
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, item -> {
// Update the UI.
});
}
}
For further information explore the android developer documentation for MVVM Architecture
https://developer.android.com/topic/libraries/architecture/viewmodel

Dynamically updating a fragment

I basically have a MainActivity that has multiple tabs. Each tab is a ShowListFragment and it extends Fragment. Now, each tab contains data that I fetch from a database. I have a MOVE-button that moves data from one tab to another in each Fragment:
#Override
public void onClick(DialogInterface dialog, int listIndex) {
database.add(listIndex,object);
database.remove(listIndex,object);
}
The fragment does not update directly, but after a few swipes in between the tabs (3 exactly). How do I force the Fragment to update instantaneous after I clicked the button? I don't want to manage it through onPageSelected in the ViewPager, since it does not update the fragment I'm currently on, but after I've swiped to the next fragment. And also I don't want to update the data after each swipe.
I know that I maybe need to use some kind of observer pattern like this: How do I make a Class extend Observable when it has extended another class too?
But still, I'm still not sure how to update the fragment directly, and how to apply the observer/event pattern in my application.
Updated Answer:
With Android architecture components, doing this is much simpler.
The recommended pattern is using a ViewModel with LiveData members. Your fragments will register observers on the LiveData members which will automatically be coordinated with lifecycle events, e.g. unregistering in onDestroy() etc. https://developer.android.com/topic/libraries/architecture/livedata
When using the Navigation component, you can pass data when navigating to a fragment: https://developer.android.com/guide/navigation/navigation-pass-data
You can also return data from the navigated fragment: https://developer.android.com/guide/navigation/navigation-programmatic
Old answer (superseded by Architecture Components):
Since the fragments can access the activity easily enough with getActivity(), I would make the activity be the central hub for dispatching updates.
It sounds like you already have the persistence part handled with the database and all you need is some update events. So here goes:
Define a listener interface. I usually do this as an inner interface within the activity:
public interface DataUpdateListener {
void onDataUpdate();
}
Add a data structure to your activity to keep track of listeners:
private List<DataUpdateListener> mListeners;
Don't forget to initialize in the constructor:
mListeners = new ArrayList<>();
Add the register/unregister methods to the activity:
public synchronized void registerDataUpdateListener(DataUpdateListener listener) {
mListeners.add(listener);
}
public synchronized void unregisterDataUpdateListener(DataUpdateListener listener) {
mListeners.remove(listener);
}
Add the event method to your activity:
public synchronized void dataUpdated() {
for (DataUpdateListener listener : mListeners) {
listener.onDataUpdate();
}
}
Have your fragments implement DataUpdateListener:
public class MyFragment extends Fragment implements DataUpdateListener {
and implement the method
#Override
public void onDataUpdate() {
// put your UI update logic here
}
Override onAttach() and onDestroy() in the fragments to register/unregister:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
((MainActivity) activity).registerDataUpdateListener(this);
}
#Override
public void onDestroy() {
super.onDestroy();
((MainActivity) getActivity()).unregisterDataUpdateListener(this);
}
Fire the event in your fragment's UI update event:
#Override
public void onClick(DialogInterface dialog, int listIndex) {
database.add(listIndex,object);
database.remove(listIndex,object);
((MainActivity) getActivity()).dataUpdated();
}

update main activity ui from a callback in login activity

I have a navigation drawer in main activity A and a login activity B. Activity B has a OnLogin callback in which i update a global userinfo from a singleton which main activity can access. Note that this OnLogin callback is invoked in a thread.
Now I want to update main activity navigation drawer header ui (e.g. user name and user icon). I'm using startActivityForResult in main activity A to get a flag from login activity B to indicate that something has changed and then update UI in OnActivityResult.
The problem I'm facing is that when activity B finishes (triggered by OnBackPressed in activity B), the OnLogin callback doesn't necessary get called. As a result, onActivityResult won't necessary pick up the change made in UserInfo. My question is, what is a proper way of updating UI in this case?
What i ended up doing
UserInfo.java
class UserInfo extends Observable {
private String value_;
public void setValue(String value)
{
value_ = value;
setChanged();
}
}
MainActivity.java
MainActivity extends AppCompatActivity implements Observer {
public void onCreate(Bundle savedInstanceState)
{
// other onCreate stuff
Singleton.getInstance().getUserInfo().addObserver(this);
// UserInfo initialization
...
// UI initialization
Singleton.getInstance().getUserInfo().notifyObservers();
}
public void update(Observable observable, Object data)
{
if (observable instanceof UserInfo)
{
//update ui
}
}
}
LoginActivity.java
OnLogin(String value)
{
UserInfo userInfo = Singleton.getInstance().getUserInfo();
userInfo.setValue(value);
userInfo.notifyObserver();
}

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