I'm using android AAC library and Android databinding library in my project. I have AuthActivity and AuthViewModel extends android's ViewModel class. In some cases i need to ask for Activity to call some methods for ViewModel.
For example when user click on Google Auth or Facebook Auth button, which initialized in Activity class (because to initialize GoogleApiClient i need Activity context which i can not pass to ViewModel, view model can not store Activity fields).
All logic with Google Api and Facebook API implemented in Activity class:
//google api initialization
googleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this, this)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
//facebook login button
loginButton.setReadPermissions(Arrays.asList("email", "public_profile"));
loginButton.registerCallback(callbackManager,
Also i need to call sign in intent which requires Activity context too:
Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient);
startActivityForResult(signInIntent, GOOGLE_AUTH);
I can not request facebook login and google login, or startActivity intent from view model class, so i created class interface AuthActivityListener:
public interface AuthActivityListener {
void requestSignedIn();
void requestGoogleAuth();
void requestFacebookAuth();
void requestShowDialogFragment(int type);
}
Implement listener in activity class:
AuthActivityRequester authRequestListener = new AuthActivityRequester() {
#Override
public void requestSignedIn() {
Intent intent = new Intent(AuthActivity.this, ScanActivity.class);
startActivity(intent);
AuthActivity.this.finish();
}
#Override
public void requestGoogleAuth() {
Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient);
startActivityForResult(signInIntent, GOOGLE_AUTH);
}
...
And assign this listener in view model class to call activity methods:
// in constructor
this.authRequester = listener;
// call activity method
public void onClickedAuthGoogle() {
authRequester.requestGoogleAuth();
}
After google or facebook authentication passed i call view model method from activity:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
callbackManager.onActivityResult(requestCode, resultCode, data);
if (requestCode == GOOGLE_AUTH) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
if (result.isSuccess()) {
GoogleSignInAccount acct = result.getSignInAccount();
if (acct != null) {
viewModel.onGoogleUserLoaded(acct.getEmail(), acct.getId());
} else {
viewModel.onGoogleUserLoaded("", "");
}
}
}
}
Can anyone explain me is this approach of communication between view model and activity is right, or i need to find another way to call activity methods from view model ?
There are several different approaches on how to do this.
Here I want to share my approach with you. Which, in my opinion, is the most suitable for MVVM pattern ideology.
As was mentioned - "View Model must know nothing about the View and reference it". This leaves not many options on how a View Model will call an Activity method. First, what comes to mind is a Listener approach. But this approach has several drawbacks in my opinion:
The View should take care of subscribing/unsubscribing to/from ViewModel, as it's lifetime most likely shorter than ViewModel's
The first drawback also leads to a situation where something happened and the ViewModel should call View's method but the View is in between subscribing/unsubscribing; ViewModel also should aware of empty listener situation as it can be null
When adding new methods of ViewModel-Activity communication you will have to make changes in ViewModel, Activity and Listener interface.
So the Listener approach doesn't suite quite well. And it looks more like an MVP approach. To eliminate the above-mentioned drawbacks (or at least some of them), I've created, what I call, ViewModel Events approach. In this approach, ViewModel "emits" (or generates) it's events and lets the View to observe them. Let me show what I'm talking about.
At first, we will need some representation of the ViewModel event.
abstract class ViewModelEvent {
var handled: Boolean = false
private set
open fun handle(activity: BaseActivity) {
handled = true
}
}
As you already can see, the handle() method will do the magic. When the Activity will handle received event it will pass its instance to handle() method as a parameter. Inside this method, we can call any Activity methods (or safe cast it to some specific Activity). The handled property is aimed to not let the Activity to handle this ViewModelEvent twice.
Further, we need to create some mechanism for the ViewModel to emit its events. LiveData suits the most for these needs. It will cancel an observer subscription on lifecycle events and it will store last emitted event (that is why the ViewModelEvent should have the above-mentioned handled property).
abstract class BaseViewModel: ViewModel() {
private val observableEvents = MutableLiveData<ViewModelEvent>()
fun observeViewModelEvents(): LiveData<ViewModelEvent> = observableEvents
protected fun postViewModelEvent(event: ViewModelEvent) {
observableEvents.postValue(event)
}
}
Nothing complex here. Just a MutableLiveData (exposed as LiveData) and a method to emit events. By the way, inside the postViewModelEvent we can check the thread this method was called from and use MutableLiveData.postValue or MutableLiveData.setValue.
And finally, the Activity itself.
abstract class BaseActivity: Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
// ...
viewModel.observeViewModelEvents().observe(this, Observer {
val event = it.takeUnless { it == null || it.handled } ?: return#Observer
handleViewModelAction(event)
})
}
protected open fun handleViewModelAction(event: ViewModelEvent) {
event.handle(this)
}
}
As you can see, general events can be handled in the BaseActivity, while some specific events can be handled by overriding the handleViewModelAction method.
This approach can be changed for specific needs. For example, ViewModelEvent doesn't have to work with Activity instance and can be used as a "marker" event or it can pass some specific parameters for the required action, etc.
The ViewModel Events approach makes ViewModel-Activity communication robust and seamless. Activity will have to subscribe once and it will not miss the latest ViewModel's event.
the most difficult part of MVVM is View model must not know about view and reference them
This is quite strong restriction.
You have some options about that
1. View model methods receveing context argument
You can make methods receveing context from view(this method is called from view).
After you can instantiate context related variables.
If you are aware about memory leak, just destroy it when view is pause or stop using Lifecycle aware AAC and reinstatiate when resume or start of Activity or Fragment.
About onActivityResult, I think your solution is not bad because API support is like that.
2. get context from view with data binding
in layout xml, you can send view itself with event listener.
<Button
....
android:onClick=“#{(view) -> vm.onClickFacebookLogin(view)}”
Then you can receive view and retrieve context from view in Viewmodel
3. Use AndroidViewModel
AndroidViewModel class is same with ViewModel class without that has Application context.
You can use Application Context with
gerApplication()
Thank you
well your approach is quite good. But somehow your interface depends on the activity means if you are reusing your view these interface makes no use or may be for that scenario you have to create new interface to solve your problem.
But if you create an instance of Activity then you have control of it.
There are two ways to call Activity's public method from viewModel.
By casting activity with context like
MonthlyAttendance activity = (MonthlyAttendance) context;
activity.getAttendance();
Here, Activity context is required in ViewModel. You can pass context in ViewModel with the help of ViewModelProvider.Factory.
With the help of MutableLiveData class and observers like
MutableLiveData callMethod = new MutableLiveData<>();
callMethod.setValue(true)
viewModel.callMethod.observe(this, this::getAttendance);
Related
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.
Previously, I was using activities in my project and was sending data using Intent from one activity to another which works perfectly fine.
Now requirement changes, and I have to show all things on Dialogs, instead of activities, so there will separate 3-4 dialog class and single activity.
Now I want the same flow on dialog also, but there is a problem to pass data temporarily exactly how intent works!
I tried with Singleton, but the problem is it remains data until the whole lifecycle, but I don't want that.
I can't use the interface also because there are lots of things to pass.
Also, I can't use bundle fundle n all those, because this all depends on runtime, I meant it depends upon if user fill input
Question: How can I pass data from one class to another class or activity? and it should not save value for the whole lifecycle.
statically sending data is an option but its not good way, because memory to static variables is assigned at Application level and can be cleared when memory needed. The best way is to use
Object Oriented approach
For example if you have a class, You can send data in class constructor, or can send it through function call
class class1
{
public class1(Object data) { // constructor
// you can use this data
}
//// Or through function call
public void func(Object data) { // this method can be called by other classes which has its object
// you can use this data
}
}
Now lets assume you have another class
class class2
{
class1 obj = new class1(your_data_object); // if you want to send through constructor
void someMethod() {
obj.func(your_data_object); // send data whatever you want to send
}
}
Obviously your case will not be as simple as my example, but to handle complex cases you can implement interfaces.
Interface Example
define an interface
interface myListener {
public void listen(Object data);
}
now lets say you want to call class2 method from class1. then class2 must implement this interface.
public class class2 implements myListener {
#override
public void listen(Object data)
{
/// you got data here, do whatever you want to do that with that data.
}
}
Now in class1 if you have interface object you can call class2 method
interfaceRef.listen(your_data);
Try with EventBus or BroadCastReceivers to pass data accordingly in local variables.
EventBus is a publish/subscribe event bus for Android and Java. EventBus... simplifies the communication between components. decouples event senders and receivers. performs well with Activities, Fragments, and background threads.
http://greenrobot.org/eventbus
First Register to EventBus in your Activity
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
Now pass the data from anywhere ,whether it is activity/fragment/background service etc etc etc like :
EventBus.getDefault().postSticky(new MessageEvent("your data here");
Now in your activity receive this message like :
#Subscribe(sticky = true,threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
Log.e("TAG","Event Received");
Log.e("TAG",event.getData);
}
I found a case when architecture components ViewModel isn't retained - in short it goes as follows:
Activity is started and ViewModel instance is created
Activity is put to background
Device screen is rotated
Activity is put back to foreground
ViewModel's onCleared method is called and new object is created
Is it normal behavior of Android that my ViewModel instance is getting destroyed in this case? If so, is there any recommended solution of keeping its state?
One way I can think of is saving it once onCleared is called, however, it would also persist the state whenever activity is actually finishing. Another way could be making use of onRestoreInstanceState but it's fired on every screen rotation (not only if the app is in background).
Any silver bullet to handle such case?
Yes #tomwyr, this was a bug from an android framework. Bug details
The fix is available in 28.0.0-alpha3 and AndroidX 1.0.0-alpha3
But if you don't want to update to above versions now itself, Then you can solve like this (I know this is a bad solution but I didn't see any other good way)
In your activity override onDestroy method and save all the required fields to local variables before calling super.onDestroy. Now call super.onDestroy then Initialize your ViewModel again and assign the required fields back to your new instance of ViewModel
about isFinishing
Below code is in Kotlin:
override fun onDestroy() {
val oldViewModel = obtainViewModel()
if (!isFinishing) { //isFinishing will be false in case of orientation change
val requiredFieldValue = oldViewModel.getRequiredFieldValue()
super.onDestroy
val newViewModel = obtainViewModel()
if (newViewModel != oldViewModel) { //View Model has been destroyed
newViewModel.setRequiredFieldValue(requiredFieldValue)
}
} else {
super.onDestroy
}
}
private fun obtainViewModel(): SampleViewModel {
return ViewModelProviders.of(this).get(SampleViewModel::class.java)
}
AFAIK, ViewModel's only purpose is to survive and keep the data (i.e. "save the state") while its owner goes through different lifecycle events. So you don't have to "save the state" yourself.
We can tell from this that it's "not normal behavior". onCleared() is only called after the activity is finished (and is not getting recreated again).
Are you creating the ViewModel using the ViewModelProvider, or are you creating the instance using the constructor?
In your activity, you should have something like:
// in onCreate() - for example - of your activity
model = ViewModelProviders.of(this).get(MyViewModel.class);
// then use it anywhere in the activity like so
model.someAsyncMethod().observe(this, arg -> {
// do sth...
});
By doing this, you should get the expected effect.
For others that may not be helped by previous answers like me, the problem could be that you haven't set up your ViewModelProvider properly with a factory.
After digging around I solved my similiar problem by adding the following method to my Activities:
protected final <T extends ViewModel> T obtainViewModel(#NonNull AppCompatActivity activity, #NonNull Class<T> modelClass) {
ViewModelProvider.AndroidViewModelFactory factory = ViewModelProvider.AndroidViewModelFactory.getInstance(activity.getApplication());
return new ViewModelProvider(activity, factory).get(modelClass);
}
And then I did this in my Fragments:
protected final <T extends ViewModel> T obtainFragmentViewModel(#NonNull FragmentActivity fragment, #NonNull Class<T> modelClass) {
ViewModelProvider.AndroidViewModelFactory factory = ViewModelProvider.AndroidViewModelFactory.getInstance(fragment.getApplication());
return new ViewModelProvider(fragment, factory).get(modelClass);
}
I already had some abstract super classes for menu purposes so I hid the methods away there so I don't have to repeat it in every activity. That's why they are protected. I believe they could be private if you put them in every activity or fragment that you need them in.
To be as clear as possible I would then call the methods to assign my view model in onCreate() in my activity and it would look something like this
private MyViewModel myViewModel;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myViewModel = obtainViewModel(this, MyViewModel.class);
}
or in fragment
private MyViewModel myViewModel;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getActivity() != null) {
myViewModel = obtainFragmentViewModel(getActivity(), MyViewModel.class);
}
}
Change support library/compileSDK/targetSDK to 28.
I had similar issue with multi-window. When switching to split screen, my viewModel is recreated. Support library 28 fixed my problem. (My lifecycle version is 1.1.1)
Would it be an anti-pattern if from a Presenter layer I open an Activity?
If so, should I manage the navigation of the app from the View Layer?
Yes it's an anti-mvp-pattern. Based on passive view in MVP, you lost your testability, because you don't have to deal with the android framework in your presenter.
So it's better to manage the navigation of the app from the View Layer.
class MyPresenter {
MyPresenter.View view;
void backButtonClicked() {
view.navigateToHomeScreen();
}
public interface View {
void navigateToHomeScreen();
}
}
class MyActivity extends Activity implements MyPresenter.View {
#Override
void navigateToHomeScreen() {
startActivity(...)
}
#OnClick(R.id.my_button)
void onClick() {
presenter.backButtonClicked();
}
}
Also another advantage of this way is that it will be easy to replace activity with a fragment or a view.
Edit 1:
Morgwai said this way will break separation of concern and single responsibility, but you cannot have single responsibility every where. Sometime you need to violate it. Here is an example from Google for MVP:
TaskDetailPresenter calls ShowEditTask which is responsible to open a new Activity inside TaskDetailFragment.
But also you can use CommandPattern which is a better approach
interface NavigationCommand {
void navigate();
}
So, Presenter will use it when it needs.
As I wrote in my comment to the accepted answer, I think that managing navigation from the view layer is a clear breaking of separation of concerns rule: views should contain ONLY methods to update current UI screen.
The problem originates from the android platform design as Activity and Fragment classes contain both methods to operate on UI screen and to send intent objects that start other activities like startActivity.
A clean way to solve this would be to create some Navigator interface that would contain methods related to navigation, make activities implement it and inject it into presenters as well. This way at least from the presenters' standpoint navigation and UI manipulation would be separated. It may however look odd from activities' standpoint: now they would often implement both interfaces (Navigator and View) and pass their reference 2 times to the presenter. If because of this reason you decide to manage navigation from your view layer then at least keep methods for navigating separate from those for manipulating UI: never perform navigation and UI manipulation in the same method.
In my opinion it would be better if you open an activity from the View Layer. I prefer that Presenter knows about Activity as little as possible.
If there is some condition of what activity should be started, you can use something like this:
public class Presenter {
private ViewsPresentation mViewsPresentation;
public void someButtonClicked() {
if (/*some condition*/) {
mViewsPresentation.startFirstActivity();
} else {
mViewsPresentation.startSecondActivity();
}
}
public interface ViewsPresentation {
void startFirstActivity();
void startSecondActivity();
}
}
I have made this solution (in Kotlin):
I created an Interface called ViewNavigator
interface ViewNavigator {
fun navigateTo(target: Class<*>)
}
Then I made the View Interface Implement it
interface View : ViewNavigator {
//...
}
Then the Actual View (the activity) can override the navigateTo function
override fun navigateTo(target: Class<*>) {
startActivity(Intent(this, target))
}
So, whenever I want to navigate to any activity, I can simply write that in the presenter class. For example:
override fun onAnimationFinished() {
view.navigateTo(HomeActivity::class.java)
}
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'