I have three components whose data I have to synchronise.
Activity - This activity get data from Content Provider and has a ViewPager. It passes this data (Fragment.getInstance(data...)) to the second component which is a Fragment
Fragment - This fragment has a RecyclerView and onClick() of a particular item, data is passed through intent to the third component which is an Activity.
Activity - Here the data which is received is modified.
What would be the best way to sync this modification across all the three components?
Do not hold references to the same POJOs (Plain old java object) between different activities and fragments. Use shared preferences or a SQL db instead.
Activities and fragments are supposed to be stateless. So I am going to tell you how to transfer data between activities and fragments rather than how to synchronize data.
Let's take them case by case:
'1 to 2': You probably already have all the fragments in the activity. So just call fragment.sendData(...) to send data to the fragment. Caveat: In case you are using a FragmentStateAdapter you will have to do this (reference):
if (adapter.getCount() > 0) {
fragment = (MyFragment) adapter.instantiateItem(
null, viewPager.getCurrentItem());
// A NullPointerException is thrown if the item isn't initialized as yet
// So be careful.
}
'2 to 1': Create a fragment callback interface (say FragmentCallback) that the activity1 must implement i.e. override Fragment.onAttach(Activity activty), check if activity instanceof FragmentCallback and throw an exception if it isn't. (This is automatically generated by android studio when you create a new fragment)
'1 to 3': Start the activity by passing an Intent with the data.
'2 to 3': Start the activity by passing an Intent with the data. In case you need some data from activity1, don't use this method. Use '2 to 1' followed by '1 to 3' instead.
'3 to 1': The correct way to do it is to start a new activity1 with Intent data or send it to the original activity1 using startActivtyForResult and handle the result in activity1. You can use android:launchMode="singleTop" (ref) if needed. If you have to send it the original activity (in a way that cannot be satisfied by startActivtyForResult), it's definitely a code smell. Use shared preferences or something and check it in Activity1.onResume and/or Activity1.onCreate.
'3 to 2': Use '3 to 1', followed by '1 to 2'.
I adopted to the event bus library which simplified the communications. Check it out at EventBus
Related
I have an application that needs to collect some data before doing it's main job.
So, the first fragment collects data, the second fragment collects data and then the third fragment uses the data.
The problem is: data in the first fragment is uncorrelated to the data I collect in the second fragment.
How can I pass the data from the first fragment to the third? Should I incrementally pass all the data I collect in the next fragment arguments, or should I store them elsewhere? I'd really like to know what the best practice is.
explicative image
I won't use a database, since I don't need to permanently store the data.
Thank you!
As for any best practices, the best answer is "it depends".
If your app is really small and simple then it's okay to pass data from one screen to another in the arguments bundle.
A more complex approach is to store data somewhere outside of these Fragment lifecycles.
It's a general rule you can implement as you want. A couple of examples below:
Data can be stored on Application class level. Application class runs for all application lifecycle. Every fragment can get Application instance from its activity like activity?.getApplication().
Data can be stored on Activity level if all fragments run in a single activity. Activity can be obtained from Fragment using activity or requireActivity().
Data can be stored on a parent fragment level if all fragments run in this parent fragment. It can be obtained from child fragments using parentFragment.
All these approaches suppose you to cast some "parent" thing to specific interface or implementation. For example, if your fragments run in the MainActivity which is stores some val data: Data as its property, then you should use it something like this: (requireActivity() as MainActivity).data
Clarification on a couple of popular answers:
The Shared ViewModel is just a special case of activity-level approach as ViewModel instance is stored in Activity scope.
The SharedPrefrences like any "database" approach is a special case of application-level approach as SharedPreferences is stored on application-level (in the file storage) no matter where you create its instance. (And SharedPreferences persists data across application starts, so it's not your option)
In addition to mentioned "Shared ViewModel" technique, Androidx introduced new "Fragment result Api" starting with "Fragment" library v1.3.0-alph04 (currently in beta) which could be used for communication between pair of Fragments or Activity-Fragment.
A Fragment/Activity set a listener in FragmentManager by specifying a key and other Fragment/Activity send data (in form of a Bundle) to the listener with that key. If there's no listener for the key, FragmentManager keeps newest data until a listener gets registered.
Pay attention that listener and result must be set on same FragmentManager instance.
I my opinion, its good for signals (events), not for sharing data. A situation I found it useful, was sending "onWindowFocusChanged" from Activity to Fragment. In case of sharing data, Shared ViewModel is better.
I have a Recyclerview in MainActivity where data are from an API. I want to open a DetailsActivity after clicking an item.
Now in MVP pattern, how should I pass the data object from MainActivity to DetailsActivity? I used Interactor in Mainactivity to handle data part.
I think you should use callback in your adapter and pass data from your adapter to
your first activity then pass data from first activity to second activity with Parcelable.
You can follow this for use Parcelable.
[https://developer.android.com/reference/android/os/Parcelable
Under an assumption DetailsActivity is supposed to show "details" that have id, you may pass the id through a Bundle to DetailsActivity and fetch "details" by the id.
If the assumption is wrong, then you might make the "details" Parcelable and pass them through a Bundle to the DetailsActivity.
By using either of the approaches it is guaranteed that the data passed through a Bundle will "survive" a process death in case your app process is killed by the system in background. I.e., when navigating back to the app the Bundle will be "redelivered" to DetailsActivity.
I can directly pass the data to the DetailActivity through Intent, but how is the MVP approach here?
In MVP, a view (V) is usually platform specific, so it's fine for it (in Android) to operate with Bundle.
That means I can directly send data to DetailActivity?
Yes, it might be as follows. Presenter (P) of DetailsActivity gets an id passed via Intent and "asks" Interactor to get the details data from Repository (or some other abstraction you use).
I made several api calls from a model class of an Activity and upon receiving responses from each of them, I need to feed the data to a Fragment one after the other by invoking multiple instances of the same fragment.
Ideally, the next fragment will only be fed with data after the previous Fragment has exited (by response from a listener).
I have looked everywhere and couldn't find a solution to this problem. I have tried using AsyncTask with a CountDownLatch to block the next api response before getting an action response from the initial Fragment but after it only invoked one Fragment (I know how many fragments I should be creating) and back to normal Activity view.
Any thoughts on how to deal with this problem?
There are 2 ways for this
Create a constructor to the fragment where you pass the data when you create instances.
mFragment1 = new xFragment(dataA);
mFragment2 = new xFragment(dataB)
To use Interface and Implementation to pass data from Activity to. Check the link below
https://developer.android.com/training/basics/fragments/communicating.html
I wonder the difference between two ways of transfering data from activity to fragment.
One is using getArgument() and setArgument(). I can transfer data using these methods at Fragment's contruction time.
Another is using getActivity() method. Like this way
((HostActivity)getActivity()).getXXX()
After declaring getter method of data Fragment may use, call this method in fragment through getActivity() and Type casting.
I think second one is easier and convenient. Because get/setArgument() can be called only Fragment's contruction time.
So, How to apply these 2 way to sending and getting data between Activity and Fragment?
A Fragment represents a behavior or a portion of user interface in an
Activity. You can combine multiple fragments in a single activity to
build a multi-pane UI and reuse a fragment in multiple activities.
Because fragment can reuse in multiple activity, if you use getActivity() with type casting, you must check instanceOf activity before call method. And each of activity use that fragment, you must implement method getXXX().
Use newInstance method in fragment, you only pass require parameter for it.
If you create fragment for individual activity, you can apply 2 ways transfer data.
The fragment has an independent lifecycle from activity with specific threads, functions and handlers. So you can use getters/setters Activity variables like a global variables and bundle data (arguments) to independent fragment variables.
I'm very new to Android programming (and Java for that matter) coming from an iOS background.
What I am trying to do, is pass a pointer to a Fragment from one Activity to another.
Basically, I have a starting activity called BeginActivity that handles a couple of Fragments for login and register screens. Once logged in, I load up the main activity of the app called TabsFragmentActivity using this code:
public void loggedIn() {
Intent intent = new Intent(this, TabsFragmentActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
this.startActivity(intent);
finish();
}
I'm using FLAG_ACTIVITY_CLEAR_TOP as I dont want the user to go back without actually logging out first.
Now the problem:
In BeginActivity I have a pointer to a fragment that holds the users data. I am using it like a singleton, that the first few view fragments can access from BeginActivity.
I need to pass this same object to the new TabsFragmentActivity before I call finish() on it.
How do I do this?
I know I can use putExtra() but I believe that is just for strings etc.. and not other Fragments.
Is there a way in the newly created TabsFragmentActivity that I can reference the BeginActivity to 'grab' the pointer?
Thanks
First of all, you should be sure about Fragments and Activity life cycle.
Fragments are designed to be reusable UI complex components. They look like activity, but you can reuse. So,you can have as many activities you need containing the same fragments, but not the same instances of these fragments.
If you just want to pass you user data for another activity you must use Bundle and putExtra(). Depending of the user data type can be necessary implements Serializable or Parcelable Interfaces, as #gheese said.
If you want to use the same UI appearence of your fragment on two or more activities, besides use Bundle and putExtra. Each activity that you want this behavior must contains a field whose is a Fragment and in the moment of starting this fragment you can use getActivity().getIntent().getExtra to get the user information and populate your fragment.
Basically you need to be able to pass your class via the intent, look at Serializable / Parcelable interfaces
This question has the answer you require
How to pass an object from one activity to another on Android