Accessibility focus is reset in Fragments - android

I have a button on the screen, when I open a new Fragment by clicking on that button with TalkBack and return to the previous Fragment (fm.popBackStack()), the button should get accessability focus again. But instead, the focus is reset to the first element on the screen as on the first visit. The same with RecyclerView, when I open a new Fragment by clicking on an item, then when I return, the focus should return to the same item on which it was. It works as expected with activity, but not with fragments. How it can be fixed?
Tested on Android 9, 11 and Fragment 1.3.0, 1.4.0, 1.4.1 versions

This will be a long answer because this question has various solutions depending on your needs, and I decided to take time and be thorough because accessibility is essential!
tl;dr solution for your problem:
To correctly save the instance state of Fragment you should do the following:
In the fragment, save instance state by overriding onSaveInstanceState() and restore in onActivityCreated():
class MyFragment extends Fragment {
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
...
if (savedInstanceState != null) {
//Restore the fragment's state here
}
}
...
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Save the fragment's state here
}
}
And important point, in the activity, you have to save the fragment's instance in onSaveInstanceState() and restore in onCreate()
class MyActivity extends Activity {
private MyFragment
public void onCreate(Bundle savedInstanceState) {
...
if (savedInstanceState != null) {
//Restore the fragment's instance
mMyFragment = getSupportFragmentManager().getFragment(savedInstanceState, "myFragmentName");
...
}
...
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Save the fragment's instance
getSupportFragmentManager().putFragment(outState, "myFragmentName", mMyFragment);
}
}
Deep dive - Managing UI state
You can efficiently save and restore the UI state by dividing the work among the various types of persistence mechanisms. In most cases, each of these mechanisms should store a different kind of data used in the activity, based on the tradeoffs of data complexity, access speed, and lifetime:
Local persistence: Stores all data you don't want to lose if you open and close the activity.
Example: A collection of song objects, including audio files and metadata.
ViewModel: Stores all the data needed to display the associated UI Controller in memory.
Example: The song objects of the most recent search and search query.
onSaveInstanceState(): Stores a small amount of data needed to quickly reload the activity state if the system stops and then recreates the UI Controller. Instead of storing complex objects here, persist the complex objects in local storage and store a unique ID for these objects in onSaveInstanceState()
Example: Storing the most recent search query.
As an example, consider an activity that allows you to search through your library of songs. Here's how different events should be handled:
The ViewModel immediately delegates this data locally when the user adds a song. If this newly added song should be shown in the UI, you should also update the data in the ViewModel object to reflect the addition of the song. Remember to do all database inserts off of the main thread.
When the user searches for a song, whatever complex song data you load from the database for the UI Controller should be immediately stored in the ViewModel object. You should also save the search query itself in the ViewModel object.
When the activity goes into the background, the system calls onSaveInstanceState(). You should save the search query in the onSaveInstanceState() bundle. This small amount of data is easy to save. It's also all the information you need to get the activity back into its current state.
Options for preserving UI state
When the user's expectations about UI state do not match default system
behaviour, you must save and restore the user's UI state to ensure that the
system-initiated destruction is transparent to the user.
Each of the options for preserving UI state vary along the following dimensions that impact the user experience:
ViewModel
Saved instance state
Persistent storage
Storage location
in memory
serialized to disk
on disk or network
Survives configuration change
Yes
Yes
Yes
Survives system-initiated process death
No
Yes
Yes
Survives user complete activity dismissal/onFinish()
No
No
Yes
Data limitations
complex objects are fine, but space is limited by available memory
only for primitive types and simple, small objects such as String
only limited by disk space or cost/time of retrieval from the network resource
Read/write time
quick (memory access only)
slow (requires serialization/deserialization and disk access)
slow (requires disk access or network transaction)
SavedStateRegistry
Beginning with Fragment 1.1.0, UI controllers, such as an Activity or Fragment, implement SavedStateRegistryOwner and provide a SavedStateRegistry that is bound to that controller. SavedStateRegistry allows components to hook into your UI controller's saved state to consume or contribute to it. For example, the Saved State module for ViewModel uses SavedStateRegistry to create a SavedStateHandle and provide it to your ViewModel objects. You can retrieve the SavedStateRegistry from within your UI controller by calling getSavedStateRegistry().
Components that contribute to the saved state must implement SavedStateRegistry.SavedStateProvider defines a single method called saveState(). The saveState() method allows your component to return a Bundle containing any state that should be saved from that component. SavedStateRegistry calls this method during the saving state phase of the UI controller's lifecycle.
class SearchManager implements SavedStateRegistry.SavedStateProvider {
private static String QUERY = "query";
private String query = null;
...
#NonNull
#Override
public Bundle saveState() {
Bundle bundle = new Bundle();
bundle.putString(QUERY, query);
return bundle;
}
}
To register a SavedStateProvider, call registerSavedStateProvider() on the SavedStateRegistry, passing a key to associate with the provider's data as well as the provider. The previously saved data for the provider can be retrieved from the saved state by calling consumeRestoredStateForKey() on the SavedStateRegistry, passing in the key associated with the provider's data.
Within an Activity or Fragment, you can register a SavedStateProvider in onCreate() after calling super.onCreate(). Alternatively, you can set a LifecycleObserver on a SavedStateRegistryOwner, which implements LifecycleOwner, and register the SavedStateProvider once the ON_CREATE event occurs. Using a LifecycleObserver lets you decouple the registration and retrieval of the previously saved state from the SavedStateRegistryOwner itself.
class SearchManager implements SavedStateRegistry.SavedStateProvider {
private static String PROVIDER = "search_manager";
private static String QUERY = "query";
private String query = null;
public SearchManager(SavedStateRegistryOwner registryOwner) {
registryOwner.getLifecycle().addObserver((LifecycleEventObserver) (source, event) -> {
if (event == Lifecycle.Event.ON_CREATE) {
SavedStateRegistry registry = registryOwner.getSavedStateRegistry();
// Register this object for future calls to saveState()
registry.registerSavedStateProvider(PROVIDER, this);
// Get the previously saved state and restore it
Bundle state = registry.consumeRestoredStateForKey(PROVIDER);
// Apply the previously saved state
if (state != null) {
query = state.getString(QUERY);
}
}
});
}
#NonNull
#Override
public Bundle saveState() {
Bundle bundle = new Bundle();
bundle.putString(QUERY, query);
return bundle;
}
...
}
class SearchFragment extends Fragment {
private SearchManager searchManager = new SearchManager(this);
...
}
Use ViewModel to handle configuration changes
ViewModel is ideal for storing and managing UI-related data while actively using the application. It allows quick access to UI data and helps you avoid refetching data from network or disk across rotation, window resizing, and other commonly occurring configuration changes.
ViewModel retains the data in memory, which means it is cheaper to retrieve than data from the disk or the network. A ViewModel is associated with an activity (or some other lifecycle owner) - it stays in memory during a configuration change. The system automatically associates the ViewModel with the new activity instance that results from the configuration change.
ViewModels are automatically destroyed by the system when your user backs out of your activity or fragment or if you call finish(), which means the state will be cleared as the user expects in these scenarios.
Unlike saved instance states, ViewModels are destroyed during a system-initiated process death. This is why you should use ViewModel objects in combination with onSaveInstanceState() (or some other disk persistence), stashing identifiers in savedInstanceState to help view models reload the data after system death.
If you already have an in-memory solution in place for storing your UI state across configuration changes, you may not need to use ViewModel.
Use onSaveInstanceState() as backup to handle system-initiated process death
The onSaveInstanceState() callback stores data needed to reload the state of a UI controller, such as an activity or a fragment, if the system destroys and later recreates that controller.
Saved instance state bundles persist through configuration changes and process death but are limited by storage and speed because onSavedInstanceState() serializes data to disk. Serialization can consume a lot of memory if the serialized objects are complicated. Because this process happens on the main thread during a configuration change, long-running serialization can cause dropped frames and visual stutter.
Do not use onSavedInstanceState() to store large amounts of data, such as bitmaps or complex data structures requiring lengthy serialization or deserialization. Instead, store only primitive types and simple, small objects such as String. As such, use onSaveInstanceState() to store a minimal amount of data necessary, such as an ID, to re-create the data necessary to restore the UI back to its previous state should the other persistence mechanisms fail. Most apps should implement onSaveInstanceState() to handle system-initiated process death.
Depending on your app's use cases, you might not need to use onSaveInstanceState() at all.

Related

Saving activity's state with onSaveInstanceState() and ViewModel

I was left with some questions regarding ViewModels after reading this:
https://developer.android.com/topic/libraries/architecture/saving-states
It says here that you should use a combination of both a ViewModel for configuration changes (like screen rotation), and using onSaveInstanceState() for all other cases where an activity is destroyed and then recreated in order to save the UI state.
My question is how do we know the way to restore the state when onCreate(Bundle) is called - should I use the ViewModel or should I use the bundle received as a parameter? When the configuration changes, onSaveInstanceState() is also called, and obviously onCreate() is always called.
If I only restore the state from a ViewModel, it won't always remain with the correct data (since the activity could have been destroyed due to other reasons than configuration changes). If I only use the bundle I save in onSaveInstanceState() then why would I use a ViewModel to begin with?
I think it's good to think of this sources as a chain.
You have 2 sources of data - ViewModel, that is faster but lives less and saved instance state that is slower but lives longer.
The rule is simple - try using your ViewModel and if it is not populated use the bundle from onSaveInstanceState().
When you do val model = ViewModelProviders.of(this).get(MyViewModel::class.java) in onCreate() you can check if you get a new instance of viewModel. Then, if it is a new instance (i.e. it's data fields are empty) you can get some basic data from your bundle, like content id, and fetch data from the backend or database based on that id, populate your new ViewModel with it and then populate your activity from the ViewModel (if you are using LiveData it will be very natural).
Next time onCreate is called you repeat the process, either populating your activity from ViewModel or populating your ViewModel using data in the Bundle and then populating your activity from your ViewModel.
Update:
Actually there is very similar approach described in the official docs. The only difference is that you pass the bundle to ViewModel and it decides if it needs fetching data, I was not specific about this mechanism.

Should I be saving persistent data from the host activity or its fragments?

This is more of a structure/design philosophy question than anything.
I have main host activity that currently saves and loads profiles for viewing by the user.
The profiles' credentials are displayed within Fragment A (user name, birthday, etc). This is done by passing the profile as an argument to the fragment before using the fragment transaction to display it.
If the user leaves or rotates their screen, how should I go about saving that profile data? Should I do the saving from the onPause within Fragment A or its HostActivity?
And what if Fragment A also allows for profile editing? When the user confirms their changes, shall I let its HostActivity know to update the main profile being held from the activity?
Or would it be better to just wait until a FragmentA.onPause is called? I suppose I could wait until something forces the fragment to call onPause, at which point I can save both the state of the fragment as well as the profile activity from the host activity.
The main thing that confuses me is: Do I need to be managing two Profile objects? The HostActivity and its FragmentA both use it. It gets a bit confusing having to run back and forth between saving, loading, and editing. Can't I just handle all of this from one class?
Okay, I have two very good and viable answers. Which one is better for my purposes though? Should I use an sqlLite database or a global Java singleton to handle my profile? Only one profile can be active per session. Saving, loading, and editing of the profile must also be taken into consideration.
If there can only ever be one profile per user in one session (as you have suggested above) why not use a Java singleton class.
private ProfileOject() {
// Exists only to defeat instantiation.
}
public static ProfileObject getInstance() {
if(instance == null) {
instance = new ProfilerObject();
}
return instance;
}
Now use getters and setters as normal.
In each activity you can get the Profile like so:
profile = ProfileObject.getInstance();
This will mean that if you make an update in Fragment A, when the activity is called, it will fetch the updated values from the profile object.
In regards to onRotate/Pause/Resume use savedInstanceState, see example below:
//Use onSaveInstanceState(Bundle) and onRestoreInstanceState
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save UI state changes to the savedInstanceState.
// This bundle will be passed to onCreate if the process is
// killed and restarted.
savedInstanceState.putBoolean("MyBoolean", true);
// etc.
super.onSaveInstanceState(savedInstanceState);
}
//onRestoreInstanceState
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// Restore UI state from the savedInstanceState.
// This bundle has also been passed to onCreate.
boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
}
Why don't you use a sqlite database for storing user profile data? Everytime you need access to user profile data you can just query it.
You can save all the relevant data in the onPause method. Using a database will ensure data persistence, resisting any fragment/activity recreation!

Saving state on List Data when App Resumes

Here is my set up.
I have a Main SherlockFragmentActivity. It swaps many ListFragments back and forth with FragmentTransaction's. To indicate loading, anytime a ListFragment loads the data I call:
setSupportProgressBarIndeterminateVisibility(true);
The problem:
When the main Activity mentioned above first starts, or the user leaves and goes to other apps and then restarts this one after an extended period of time, the SherlockFragmentActivity seemingly reloads, there is no progress dialog in the ActionBar, the screen is white for a few seconds, and then the list data repairs (The length depends on the data connection).
Here is some supplemental code: When the main/base Activity first loads, this is one of the first things I do in the onCreate():
// Set up Main Screen
FragmentTransaction t2 = this.getSupportFragmentManager().beginTransaction();
SherlockListFragment mainFrag = new FollowingFragment();
t2.replace(R.id.main_frag, mainFrag);
t2.commit();
FollowingFragment is the one that will always load in this instance. It contains a ListView and an AsyncTask pulling data from a MySQL database.
My question: How do I prevent this delay? And how do I handle maintaining the data when user leaves for extended periods of time?
This is the normal behavior, it happens because your activity has been killed to save memory for other apps, when your app was in the background. And when it comes back to the foreground, the system recreate your activity, which will recreate your fragment.
But if your really want to avoid recreating your fragment, you can use setRetainInstance in your fragment's onCreate method:
public void setRetainInstance (boolean retain)
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
onCreate(Bundle) will not be called since the fragment is not being re-created.
onAttach(Activity) and onActivityCreated(Bundle) will still be called.
And use something like this in your FragmentActivity's onActivityCreated method:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
FragmentManager fm = getFragmentManager();
// Check to see if we have retained the worker fragment.
mRetainableFragment = (RetainedFragment)fm.findFragmentByTag("fragmentTag");
// If not retained (or first time running), we need to create it.
if (mRetainableFragment == null) {
mRetainableFragment = new RetainedFragment();
// Tell it who it is working with.
mRetainableFragment.setTargetFragment(this, 0);
fm.beginTransaction().add(mRetainableFragment, "fragmentTag").commit();
}
}
But be aware that, this should only be use for headless fragment (fragment without UI, i.e return null in onCreateView, aka worker fragment). You can still use this method for UI fragment though but it is not recommanded by google, in that case the data must be stored as member (field) in your activity. If the data which should be stored is supported by the Bundle class, you can use the onSaveInstanceState() method to place the data in the Bundle, and retrieve that data in the onActivityCreated() method.
Moreover this only works if the fragments is not added to the backstack.
According to the Android developer reference page on Activity, you have to request the progress bar feature before calling setSupportProgressBarIndeterminateVisibility():
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.activity_main);
setSupportProgressBarIndeterminateVisibility(true);
The other issue, reloading the fragments, is due to Android killing your ListFragment so that they have to reload could be resolved by overriding onSaveInstanceState(Bundle outState) and caching your data there to be retrieved in your ListFragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(savedInstanceState != null) {
// retrieve data from Bundle here
} else {
// no data, we need to reload from network
}
// initialize your View here
}
This method is not guaranteed to run all the time, however (it's not in the Fragment lifecycle). As such, you should also make sure you cache the data in onPause() and use it instead of always loading from a network connection.
#Override
public void onPause() {
super.onPause();
SharedPreferences prefs = getActivity().getSharedPreferences();
SharedPreferences.Editor editor = prefs.edit();
// put your data here using editor
editor.commit();
}
Then you can load this data in your onCreateView() by retrieving an instance of SharedPreferences and using prefs.getString(String key) and other methods.
When your app get killed, you lose your activity state and data! There are two scenarios that I can assume about your AsyncTask:
1. you are pulling some data from a Webserver. In this case I personally think caching your data which you retrieved from webserver is a better solution than implementing serializable.
2. You are pulling lots of data from local database (which causes retrieving data to take some time). In this scenario I suggest retrieving only as much data as you need, not more! (for example you can retrieve 20 items, and when user scrolling to the end of ListView retrieve next 20 items).
This solution helps your application retrieve data faster.
PS: To give you a clue how to implement the WebserviceModule with cache capability, which I assume is located in your AsyncTask, you can save every response from webserver in the SDCard and every time you trying to retrieve some resource from webserver, you should check the SDCard to see if your request already sent and cached! For every request, your should make a unique signature base on url and post parameters to recognize cached files.
When you return to activity after extending period of time, the whole app being restarted. So You can't rely on object variables to save data.
So You could avoid delay You've mentioned with saving data to some local storage in activity onStop() method. For example, shared preferences.
And when You call onCreate(), check whether You have data saved and use it if exists (and clean up to have "clean" start next time), otherwise start asynctask.

Save multiple instances states of the same Activity in Android

I am developing an Android app and I would like to avoid reloading similar data when it comes from the same Activity using the same extra.
Specifically, when I launch my Activity 'A' with extra, I use this extra to load remote data from server.
From this Activity, I can relaunch 'A' with different extra and so on.
Example :
A:id1 --> A:id2 --> A:id3
But, it can also be an extra that I already loaded :
A:id1 --> A:id2 --> A:id3 --> A:id1
In this case, I wouldn't request the server again or lose the activities stack.
As I understand, "onSaveInstanceState" allows to save one instance of one Activity, but in my case, it's multiple instances of same Activity (with differents extras).
So, is it the solution to manage a list of saved instance states (bundle) for the same Activity ? Or something else ?
Thanks in advance
The onSaveInstanceState method isn't used in the way you describe. Check this out, it's the documentation for the Activity Class, specifically the Activity Lifecycle section. onSaveInstanceState is a method that gets called when the OS has to kill an Activity for some reason. It allows you to populate a Bundle which will help recreate that specific instance of the Activity where the user left off. Usually this happens because the user switched to a different app and the OS is killing the Activity to reclaim memory, but also happens on screen rotation, so it's a nuance of the platform that is important to at least be aware of.
As for your question, what I would do is use a database to store the information that is retrieved from the server. When you start an Activity, you can first check to see if the data that needs to populate that Activity exists in the database. If it does, load and display it from there, else make the server call.
This is nice, because the data will be persistent over multiple uses of the App. Going further, if the data from the server has the potential to be stale, you can easily extend this to display the data from the database initially, and fire off an asynchronous request for the data that will update both the UI and database when it returns. Your user will almost never be in a state where they're waiting for things to load, which is always a good thing!
Here's a good tutorial on the basics of implementing an sqlite database. This will also give you the added benefit of keeping the data stored over separate runs of your application.
As an alternative, if you don't really need the persistence or other features of the database and don't think that the overhead is worth it, you could create a Singleton class which keeps track of the data as its returned, perhaps implementing it using the Application class. It's important to note (and bringing us full circle) that any in-memory method of storing this data should be managed with onSaveInstanceState to ensure you don't lose any data if the Activity is killed at an unexpected time.
+1 for MattDavis' answer, but I'd also suggest you use what's known as the "singleton pattern".
Essentially, this is a way to cache things in memory. You create a class whose purpose is to hold all the database data you don't want to keep reloading. There's a single global instance of this object, which is initially null. You call a static method that returns that instance. If the static method finds that the instance is null, it creates the instance, populates it from the database, caches it, and returns it to the caller. From that point on, all requests for the instance just return the cached copy. If your app gets killed by the system for lack of resources, and started again later, it transparently re-creates the instance as needed.
This is a very common implementation, and works in a multi-threaded environment:
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
// This is the place where you initialize the instance
// from the database.
}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
I use this pattern in a lot of places; it's very handy.
To really do it right, you should also implement onLowMemory() or onTrimMemory() in your activities to release the singleton instance when resources are tight.

Android: Transferring strings / values within Tabs, each tab having its own activity (other than Getters-Setters)

I have a tabbed layout in my application, and want the settings part to be another tab in the UI. The settings involve entering a quantity / number of entities present (e.g.: number of balls). This data is then required to be transferred to the other tabs, which process the input data, and process the display within the respective tabs accordingly (say the number of balls= 3, then there should be 3 buttons in the 2nd tab, if balls= 1, then only 1 button, and so on...). I want to avoid the data being sent to the parent activity, and then to the respective child activities, which requires the whole app to restart [startActivity()], and individual transfer is not possible, since startActivity() for child activities would result in only the child activity contents being displayed. In which case I have to ultimately use the Getters and Setters. Is there any other / better method which I can follow in this case...?
You can try onPause/onResume methods.
When tab changed, activity goes to onpause. I override onPause method for save variables to different public class. Than load back variables onResume and onCreate methods.
#Override
public void onPause()
{
super.onPause();
fileC.fileFrom = fileFrom;
fileC.fileName = fileName;
fileC.fileTo = fileTo;
fileC.Process = fileProcess;
Log.w("onPause", "onPause!!!!!!!!");
}
#Override
public void onResume()
{
super.onResume();
if( fileC.Process != null )
{
fileFrom = fileC.fileFrom;
fileTo = fileC.fileTo;
fileName = fileC.fileName;
fileProcess = fileC.Process;
}
Log.w("onResume", "onResume!!!!!!!!");
}
Singletons are not the best choice for Android application because Dalvik VM may load singleton class twice and destroy all your stored data.
Also storing data in your own Application object not suitable because system may kill this object on low memory and does not restore its state on resume.
The best way, imho, is sending data via Intent object or getters/setteres, storing in at onPause method.
Why can't you use a singleton class and static class variables to pass this data?.

Categories

Resources