Communication objects between multiple fragments in ViewPager - android

I have 5 fragments in ViewPager used to fill business object with several fields step by step, in each step some of those fields will be set. I've read many articles about communication between fragments but I'm not feeling comfortable the way others preferred, so after thinking about HOW should I do this in my case, finally I start thinking to use singleton model object which all fragments can easily access to its fields and fill them in specific steps.
As I'm new to android I want to hear from experts about using singleton instead of passing data between fragments such as implemented interface(It seems its so complicated and hard to maintenance). Any advice will be helpful.

While singleton approach seems easy to implement and understand it is way not to best way to achieve what you need. One reason is that your model object or as you call it business object lives outside of your activity's context which can create hard to find bugs. E.g. in case when more than one instance of your activity class is created by system and both keep reference to your singleton. See how you lose track of your objects?
What I would do is
Make my model object to implement Parcelable you will hate it at the beginning but once you get use to it it will become your model's best friend
Since your model is parcelable now you can easily pass it between fragments, activities, and even save it in shared preferences. One important thing to note here when you pass your parcelable between fragment or activity it is like pass by value, i.e. every time new instance is created.
Set your fragment's argument or if it is already instantiated then get arguments and add your model. here is an example:
if a fragment is not active yet:
Bundle args = new Bundle();
args.putParcable("businessObject", yourBusinessObjectThatIsParcable);
yourFragment.setArguments(args);
Otherwise:
yourFragment.getArguments().putParcelable("businessObject", yourBusinessObjectThatIsParcable);
In your fragment perhaps in onCreateView method get your model object like this MyParcableObject mpo = (MyParcableObject)getArguments().getParcelable("businessObject") and use it set whatever data you want.
When you finish editing your object on button click or in onPause method updated your fragment's arguments same way getArguments().putParcelable("businessObject", mpo);
in your last page or last fragment you can pass your object to your activity, here is how to do it
Even though it looks cumbersome but it is a practice that you need to get used to as an android developer. You get lot more control when your model implements parcelable.
Another way to do what you need is thru Delegation Pattern but it is mostly used for callbacks even though you can pass objects as well.

I wouldn't recommend a global singleton. There are two main reasons:
By definition, a singleton limits your app to a single instance of the main business object. If you (or a designer, or your boss's boss's boss) ever decide to have multiple of these ViewPagers at a time, you will have to change your architecture anyways.
The "Android way of thinking" is to expect that your user may put your app in the background and use other apps before returning to your app. If the system decides to kill your app in the background, then your singleton memory object will be destroyed, and your user will have lost all of their progress. The correct Android way to save state is by keeping the state in an Activity or Fragment, saving it appropriately in onSaveInstanceState(), and restoring it in onCreate().
All of the Fragments in the ViewPager can get a reference to the parent Activity via a call to getActivity(). Or if your ViewPager is within a Fragment, then all of the Fragments can access the parent Fragment via a call to getParentFragment(). You can then cast the result to the appropriate class (or better yet, interface) and make method calls to pass data back and forth. Keep track of your business data in the parent Activity/Fragment. This way, you don't need a global singleton
For example,
public class MyParentFragment extends Fragment {
private String mPageOneData;
private int mPageTwoData;
private List<Date> mPageThreeData;
public void setPageOneData(String data) {
mPageOneData = data;
}
...
}
public class PageOneFragment extends Fragment {
private void sendDataToParent(String data) {
Fragment f = getParentFragment();
if (f != null && f instanceof MyParentFragment) {
MyParentFragment parent = (MyParentFragment) f;
f.setPageOneData(data);
}
}
}

you can save your data in onSaveInstanceState() event of the activity in case your process will go into the background.
you can restore your data in onCreate() event by using Bundle and getExtras().
you can save your data in application class and the data will still be there in case your process will go into the background.
i prefer the first option because you don't want to make a mess in the application class with all the data from different activities and fragments.
I hope i could help :)

Have you checkout EventBus?
I'm not sure if it is the best approach, specially when your question is too broad, however it will be cool with just 5 fragments.
Hope it helps

I suppose in your MainActivity there is a ViewPager, and FragmentOne will be one of the fragments inside the view pager. Here the MainActivity is communicating to the FragmentOne to refreshhis adapter. Hope is clear.
In your MainActivity add this interface:
public interface Updateable {
public void update();
}
Implement this interface in a fragment that needs to be updated, and write the code to notify the adapter inside the update method:
public class FragmentOne extends Fragment implements MainActivity.Updateable {
...
#Override
public void update() {
// YOUR CODE TO UPDATE HERE, FOR EXAMPLE, HERE I'M UPDATING THE ADAPTER
if ( adapter != null ) {
adapter.notifyDataSetChanged();
} else {
Log.d("LOG_TAG", "null");
}
}
...
}
Call the update method from the MainActivity when the fragment loads first. You can do this overriding the getItemPosition method in your PagerAdapter, like this:
#Override
public int getItemPosition(Object object) {
if ( object != null && object instanceof FragmentOne ) {
FragmentOne f = (FragmentOne) object;
f.update();
}
return super.getItemPosition(object);
}
Finally, you have to call notifyDataSetChanged() of your viewPager adapter. This will force the adapter of your viewpager to call the getItemPosition method.
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
int previousState;
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrollStateChanged(int state) {
if (previousState == ViewPager.SCROLL_STATE_SETTLING && state == ViewPager.SCROLL_STATE_IDLE) {
if ( viewPagerAdapter.getItem(viewpager.getCurrentItem()) instanceof Pictures ) {
Log.d("LOG_TAG", "New Position=" + viewpager.getCurrentItem());
viewPagerAdapter.notifyDataSetChanged();
}
}
previousState = state;
}
});

Before choosing any option, keep in mind user can navigate or open any other app(s) so you lost your data.
You can use onSaveInstanceState but it will somehow difficult to maintain (as you said you are new in android). You can go with with singleton by using
Database - Use when you want to store maintain multiple records but you have to create a database getter/setter or use any ORM like RushOrm etc.
SharefPreference(preferably) - If you want to use single values.
In both cases you will create a singleton object and access its properties in your fragments.

make your objects parcelable and then pass it to other fragments using bundle. i.e bundle.putParcelable(obj) parcelable is very efficient and fast.
it should motivate you
http://www.developerphil.com/parcelable-vs-serializable/

Related

How to pass complex, non serializable object to android fragments

Hello fellow Android developers,
I wanna know how do you guys pass complex non serializable (& non parcelable) object to fragments. (such as Listener, Api client, ...)
Let me explain my use case:
The use case
I'm building an Android application composed of one "host" activity and 3 fragments.
Currently I'm passing the object using a custom constructor on the fragment (bad practice I know).
The fragments constructors looks like the following:
/**
* Do not remove ever or you'll face RuntimeException
*/
public FirstFragment() {
}
public FirstFragment(Session session,
ApiClient apiClient,
FirebaseAnalytics firebaseAnalytics) {
mSession = session;
mApiClient = apiClient;
mFirebaseAnalytics = firebaseAnalytics;
}
And I'm using them in the host activity like this
private FirstFragment getFirstFragment() {
if (mFirstFragment == null) {
mFirstFragment = new FirstFragment(mSession, mApiClient, mFirebaseAnalytics);
}
return mHomeFragment;
}
[...]
private void loadFragment(Fragment fragment, String tag) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.frame_container, fragment, tag);
transaction.commit();
}
[...]
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case FIRST_FRAGMENT_RES_ID:
toolbar.setTitle(R.string.first_fragment_title);
loadFragment(getFirstFragment(), "first_fragment");
return true;
[...]
}
return false;
}
};
This solution works well almost all the time. But sometimes (and I don't know when exactly) the default constructor is invoked and therefore all local members are null.
Possible solutions
To solve the problem I'm thinking about the following solutions:
Singletons, singletons everywhere
Most of the objects I'm passing are singletons therefore I can access them in the default constructor of the fragments:
public FirstFragment() {
mSession = Session.getInstance(getContext());
mApiClient = ApiClient.getInstance(getContext());
mFirebaseAnalytics = FirebaseAnalytics.getInstance(getContext());
}
Problems
However the above solution wouldn't work if I need to pass a callback or something. How can it be done like this then?
Access the objects using parent activity
I think it's one of the ugliest possible solutions because it will couple the Fragments to the parent activity. The idea is something like this
public FirstFragment() {
mSession = Session.getInstance(getContext());
mApiClient = ApiClient.getInstance(getContext());
mFirebaseAnalytics = FirebaseAnalytics.getInstance(getContext());
mListener = (Listener) getActivity(); // <- will works because parent activity implement the interface
}
Using broadcast & receiver
The idea is to keep passing singleton everywhere and use broadcast & receiver instead of listener.
How do you guys managed this scenario?
Thanks in advance !
You probably want to look into dependency injection (using a tool like Dagger or alternatives), especially for objects like an Api Client. Post the setup, you'd define, just once, how an Api Client instance could be constructed. And later you can use it pretty much everywhere with a one-line statement. The instance is guaranteed to be available upon the fragment instantiation. Further reading: https://dagger.dev/tutorial/
According to your use case, it might be easier to use a ViewModel and store your objects there. Your ViewModel will be shared across your fragments and your host
activity.
See https://developer.android.com/topic/libraries/architecture/viewmodel
Have you considered using "Shared" ViewModel?
Essentially, a sub-class of ViewModel (which is class designed to store and manage UI-related data in a lifecycle conscious way for activities and fragments) can be created like below,
class SharedViewModel : ViewModel()
Inside this class you can have your custom objects with their correct state
Next, in your 1st Fragment you can obtain a handle to this SharedViewmodel like below,
class MasterFragment : Fragment() {
private lateinit var model: SharedViewModel
And obtain the handle to it using below code,
model = activity?.run {
ViewModelProviders.of(this)[SharedViewModel::class.java]
}
You can write your own logic/method/flow inside SharedViewModel to manipulate any custom object's states.
And once all this is done, In your 2nd Fragment, you can create the handle to SharedViewModel similar to above code and using SharedViewModel object you can retrieve the "modified" custom object from same SharedViewModel
It's been several months and I have now come up with a different solution.
For the UI related data
For the UI related stuff I'm now using the androidx livedata
For the complex non serializable data
My use case was to pass complex object to the fragment, such as manager, parent activity (trough a listener), etc... The approach I have taken is by injecting these data manually from the parent activity.
The first things to do was to remove the objects from the fragment constructor and use the default constructor instead, so that I won't face any instantiation errors.
Then I have created an inject() method on the fragment classes that look like this:
public void inject(BillingManager billingManager, Listener listener) {
mBillingManager = billingManager;
mListener = listener;
}
Each fragment will have their own inject method width the objects that should be injected as parameters.
In the parent activity I have override the onAttachFragment() method to handle the fragment attach process:
#Override
public void onAttachFragment(#NonNull Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment.getClass().equals(FirstFragment.class)) {
((FirstFragment) fragment).inject(mBillingManager, this);
} else if (fragment.getClass().equals(HomeFragment.class)) {
((HomeFragment) fragment).inject(this);
}
}
Simple, and now everything work great.

Is there a way to manage fragment flow navigation dynamically using something like LiveData observers?

I'm newbie to Android development and I recently started to create a new application.
Consider that you have an application with 2 activities :MainActivity & TourneeFlowActivity. Also, you have 3 Fragments (I will call them StartTourneeFragment, ValidateTourneeFragment and EndingTourneeFragment for the example).
I've implemented Room and Repository pattern for retrieving local and remote data. Also, I've implemented ViewModels to refresh and manage UI data (LiveData) using observers.
Inside my TourneeFlowActivity, I have a fragmentNavigationImplementation (object class), for my fragment navigation flow (for ex: switch from StartTourneeFragment to ValidateTourneeFragment, using a callback listener in activity).
For now, fragment navigation is only called on user events caughted (such as a click on a button Next or Previous.
Could it be possible to use observers on object property change to trigger something like a callback which could change fragment dynamically?
Code example is not what I've done, but for illustrating what kind of mechanism I would like to do ...
I hope I'm clear with that.
public class TourneeFlowActivity extends Activity {
private FragmentNavigationImpl fragmentNavigation;
private BordereauViewModel bordereauViewModel;
#Override
public void onCreate() {
/// Some initializations
this.bordereau = ViewModelProviders.of(this).get(BordereauViewModel.class);
}
#Override
protected onActivityCreated(){
this.bordereauViewModel.getCurrentBorderau().observe(this, new Observer<Bordereau>{
#Override
public onChanged(#Nullable Bordereau bordereau) {
// Here my value has been changed so I would start a new fragment dynamically
if(bordereau != null && bordereau.getStep() == MyEnum.STEP_TO_ENDING) {
if(fragmentNavigation.getCurrentFragment()
instanceof ValidateTourneeFragment)
{
EndingFragment fragment = new EndingFragment();
fragmentNavigation.nextFragment(fragment);
}
}
}
});
}
}

What is the difference between 2 ways to set arguments of fragment

I want to pass a data object to Fragment, here are two ways to do this:
public class MyFragment extends Fragment {
private Serializable way1;
private Serializable way2;
public void setDataWay1(Serializable way1) {
this.way1 = way1;
}
public void setDataWay2(Serializable way2) {
Bundle data = new Bundle();
data.putSerializable("data", way2);
setArguments(data);
}
}
So, what is the difference between the 2 ways? Sometimes, way1 may cause NullPointerException,why? If I want to pass a OnClickListener to Fragment, what should I do?
While both methods can set the appropriate data to your fragment for first time initialization. Note that fragments will be recreated and destroyed by the system (for example on screen rotation). When that happens the system will not really call the setter way (method 1) hence, it will be a giant FAIL. Therefore, it is recommended to use the setArguments() way.

Communicating between a fragment and an activity - best practices

This question is mostly to solicit opinions on the best way to handle my app. I have three fragments being handled by one activity. Fragment A has one clickable element the photo and Fragment B has 4 clickable elements the buttons. The other fragment just displays details when the photo is clicked. I am using ActionBarSherlock.
The forward and back buttons need to change the photo to the next or previous poses, respectively. I could keep the photo and the buttons in the same fragment, but wanted to keep them separate in case I wanted to rearrange them in a tablet.
I need some advice - should I combine Fragments A and B? If not, I will need to figure out how to implement an interface for 3 clickable items.
I considered using Roboguice, but I am already extending using SherlockFragmentActivity so that's a no go. I saw mention of Otto, but I didn't see good tutorials on how to include in a project. What do you think best design practice should be?
I also need help figuring out how to communicate between a fragment and an activity. I'd like to keep some data "global" in the application, like the pose id. Is there some example code I can see besides the stock android developer's information? That is not all that helpful.
BTW, I'm already storing all the information about each pose in a SQLite database. That's the easy part.
The easiest way to communicate between your activity and fragments is using interfaces. The idea is basically to define an interface inside a given fragment A and let the activity implement that interface.
Once it has implemented that interface, you could do anything you want in the method it overrides.
The other important part of the interface is that you have to call the abstract method from your fragment and remember to cast it to your activity. It should catch a ClassCastException if not done correctly.
There is a good tutorial on Simple Developer Blog on how to do exactly this kind of thing.
I hope this was helpful to you!
The suggested method for communicating between fragments is to use callbacks\listeners that are managed by your main Activity.
I think the code on this page is pretty clear:
http://developer.android.com/training/basics/fragments/communicating.html
You can also reference the IO 2012 Schedule app, which is designed to be a de-facto reference app. It can be found here:
http://code.google.com/p/iosched/
Also, here is a SO question with good info:
How to pass data between fragments
It is implemented by a Callback interface:
First of all, we have to make an interface:
public interface UpdateFrag {
void updatefrag();
}
In the Activity do the following code:
UpdateFrag updatfrag ;
public void updateApi(UpdateFrag listener) {
updatfrag = listener;
}
from the event from where the callback has to fire in the Activity:
updatfrag.updatefrag();
In the Fragment implement the interface in CreateView do the
following code:
((Home)getActivity()).updateApi(new UpdateFrag() {
#Override
public void updatefrag() {
.....your stuff......
}
});
To communicate between an Activity and Fragments, there are several options, but after lots of reading and many experiences, I found out that it could be resumed this way:
Activity wants to communicate with child Fragment => Simply write public methods in your Fragment class, and let the Activity call them
Fragment wants to communicate with the parent Activity => This requires a bit more of work, as the official Android link https://developer.android.com/training/basics/fragments/communicating suggests, it would be a great idea to define an interface that will be implemented by the Activity, and which will establish a contract for any Activity that wants to communicate with that Fragment. For example, if you have FragmentA, which wants to communicate with any activity that includes it, then define the FragmentAInterface which will define what method can the FragmentA call for the activities that decide to use it.
A Fragment wants to communicate with other Fragment => This is the case where you get the most 'complicated' situation. Since you could potentially need to pass data from FragmentA to FragmentB and viceversa, that could lead us to defining 2 interfaces, FragmentAInterface which will be implemented by FragmentB and FragmentAInterface which will be implemented by FragmentA. That will start making things messy. And imagine if you have a few more Fragments on place, and even the parent activity wants to communicate with them. Well, this case is a perfect moment to establish a shared ViewModel for the activity and it's fragments. More info here https://developer.android.com/topic/libraries/architecture/viewmodel . Basically, you need to define a SharedViewModel class, that has all the data you want to share between the activity and the fragments that will be in need of communicating data among them.
The ViewModel case, makes things pretty simpler at the end, since you don't have to add extra logic that makes things dirty in the code and messy. Plus it will allow you to separate the gathering (through calls to an SQLite Database or an API) of data from the Controller (activities and fragments).
I made a annotation library that can do the cast for you. check this out.
https://github.com/zeroarst/callbackfragment/
#CallbackFragment
public class MyFragment extends Fragment {
#Callback
interface FragmentCallback {
void onClickButton(MyFragment fragment);
}
private FragmentCallback mCallback;
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt1
mCallback.onClickButton(this);
break;
case R.id.bt2
// Because we give mandatory = false so this might be null if not implemented by the host.
if (mCallbackNotForce != null)
mCallbackNotForce.onClickButton(this);
break;
}
}
}
It then generates a subclass of your fragment. And just add it to FragmentManager.
public class MainActivity extends AppCompatActivity implements MyFragment.FragmentCallback {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction()
.add(R.id.lo_fragm_container, MyFragmentCallbackable.create(), "MY_FRAGM")
.commit();
}
Toast mToast;
#Override
public void onClickButton(MyFragment fragment) {
if (mToast != null)
mToast.cancel();
mToast = Toast.makeText(this, "Callback from " + fragment.getTag(), Toast.LENGTH_SHORT);
mToast.show();
}
}
Google Recommended Method
If you take a look at this page you can see that Google suggests you use the ViewModel to share data between Fragment and Activity.
Add this dependency:
implementation "androidx.activity:activity-ktx:$activity_version"
First, define the ViewModel you are going to use to pass data.
class ItemViewModel : ViewModel() {
private val mutableSelectedItem = MutableLiveData<Item>()
val selectedItem: LiveData<Item> get() = mutableSelectedItem
fun selectItem(item: Item) {
mutableSelectedItem.value = item
}
}
Second, instantiate the ViewModel inside the Activity.
class MainActivity : AppCompatActivity() {
// Using the viewModels() Kotlin property delegate from the activity-ktx
// artifact to retrieve the ViewModel in the activity scope
private val viewModel: ItemViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.selectedItem.observe(this, Observer { item ->
// Perform an action with the latest item data
})
}
}
Third, instantiate the ViewModel inside the Fragment.
class ListFragment : Fragment() {
// Using the activityViewModels() Kotlin property delegate from the
// fragment-ktx artifact to retrieve the ViewModel in the activity scope
private val viewModel: ItemViewModel by activityViewModels()
// Called when the item is clicked
fun onItemClicked(item: Item) {
// Set a new item
viewModel.selectItem(item)
}
}
You can now edit this code creating new observers or settings methods.
There are severals ways to communicate between activities, fragments, services etc. The obvious one is to communicate using interfaces. However, it is not a productive way to communicate. You have to implement the listeners etc.
My suggestion is to use an event bus. Event bus is a publish/subscribe pattern implementation.
You can subscribe to events in your activity and then you can post that events in your fragments etc.
Here on my blog post you can find more detail about this pattern and also an example project to show the usage.
I'm not sure I really understood what you want to do, but the suggested way to communicate between fragments is to use callbacks with the Activity, never directly between fragments. See here http://developer.android.com/training/basics/fragments/communicating.html
You can create declare a public interface with a function declaration in the fragment and implement the interface in the activity. Then you can call the function from the fragment.
I am using Intents to communicate actions back to the main activity. The main activity is listening to these by overriding onNewIntent(Intent intent). The main activity translates these actions to the corresponding fragments for example.
So you can do something like this:
public class MainActivity extends Activity {
public static final String INTENT_ACTION_SHOW_FOO = "show_foo";
public static final String INTENT_ACTION_SHOW_BAR = "show_bar";
#Override
protected void onNewIntent(Intent intent) {
routeIntent(intent);
}
private void routeIntent(Intent intent) {
String action = intent.getAction();
if (action != null) {
switch (action) {
case INTENT_ACTION_SHOW_FOO:
// for example show the corresponding fragment
loadFragment(FooFragment);
break;
case INTENT_ACTION_SHOW_BAR:
loadFragment(BarFragment);
break;
}
}
}
Then inside any fragment to show the foo fragment:
Intent intent = new Intent(context, MainActivity.class);
intent.setAction(INTENT_ACTION_SHOW_FOO);
// Prevent activity to be re-instantiated if it is already running.
// Instead, the onNewEvent() is triggered
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
getContext().startActivity(intent);
There is the latest techniques to communicate fragment to activity without any interface follow the steps
Step 1- Add the dependency in gradle
implementation 'androidx.fragment:fragment:1.3.0-rc01'

Dynamically change ViewpagerIndicator Fragment Content Android

I am working on an application using viewpagerindicator.
In my main activity that has the viewpagerindicator, I spin off a thread that does some computation and updates a an instance variable mString of the activity. I want to update a fragment in the viewpagerindicator with the mString. However, I can't seem to figure out the best way to reach the fragment.
Does anyone know of any good samples that do something similar to this?
Create a callback object in your Fragment, register it with your FragmentActivity. If mString is already set in FragmentActivity then you can return it immediately via the callback, otherwise, when the computation thread finishes, it can return the string via the callback. The callback method should do whatever the Fragment needs to do with the string, e.g. set the text of a TextView.
E.g. create an interface called DynamicDataResponseHandler as follows:
public interface DynamicDataResponseHandler {
public void onUpdate(Object data);
}
Then in your Fragment, implement that interface as follows:
private class MyStringDataResponseHandler implements DynamicDataResponseHandler {
#Override
public void onUpdate(Object object) {
mYourTextView.setText((String)object);
}
}
Your Fragment can then instantiate a MyStringDataResponseHandler object in its onCreate, pass that to the FragmentActivity via a method in the FragmentActivity like:
private MyStringDataResponseHandler mMyStringDataResponseHandler;
public void registerMyStringDataResponseHandler (DynamicDataResponseHandler callback) {
mMyStringDataResponseHandler = callback;
if(mString != null) {
mMyStringDataResponseHandler.onUpdate(mString);
}
}
And wherever in your Handler you obtain the value for mString, do something like this:
if(mMyStringDataResponseHandler != null) {
mMyStringDataResponseHandler.onUpdate(mString);
}
Do some reading on the concept of Callbacks to get a better understanding of what I'm doing above and other ways you can use them.
You want to update the UI of a Fragment in ViewPager after it is started, do i make it clear?
Ok, in this situation
You should add a public method in your custom Fragment.
Find the Fragment in your Activity.
Invoke the method after your calculation is done.
The question is same with this one.

Categories

Resources