OnClickListeners: Preventing memory leaks - android

Tapping the button multiple times opens many new screens(Activities). To prevent this I attached a flag to prevent this. But it may cause memory leaks. After searching a lot I found one solution. It is to attach the OnClick listeners onResume and set them null on onDetach and when the button is clicked. But I am finding it difficult to implement this.
My Code :-
private static int flag = 0;
#Override
public void onStart() {
super.onStart();
binding.createEventFab.setOnClickListener(view ->{
Intent intent = new Intent(getActivity(), CreateEventActivity.class);
if(flag == 0){
startActivity(intent);
flag++;
}
});
}
#Override
public void onResume() {
super.onResume();
flag = 0;
}
Thanks in advance!

I don't think so there is a leak here, but the real problem is that you are allowing user to press button multiple times, so you need debouncing listener.
The best what you can do is use RxBinding, since you can easy use it in another classes.
Here is sample code below:
RxView.clicks(/* Your view */)
.throttleFirst(2, TimeUnit.SECONDS)
.subscribe(s -> { /* Your code */ });
or in Kotlin, with nice extension
fun View.actionOnClick(function: () -> Unit): Disposable {
return clicks()
.throttleFirst(2, TimeUnit.SECONDS)
.subscribe { function() }
}
Don't forget to add these to CompositeDisposable and clear() them in onDestroy() (for Activity) or onDestroyView() (for Fragment) method.
Another option is use ButterKnife with #OnClick(/* viewId */), but RxBinding is preferred if you would like to easy switch to Kotlin in future (and for Kotlin, another possibility is use coroutines, but deboucers using coroutines can be hard to implement for some cases, like watcher for EditText).

You can set your click listeners in a normal way. And you can set the launchMode of the activity to singleTop in your Manifest.xml file. Your activities shouldn't leak. For further reading you can take a look here about the launchMode

Related

Android - What is the best practice for passing back information from an activity without onActivityResult()?

I have a rather general question.
Assuming I have a RecyclerView in some kind of a MainActivity. The content of this RecyclerView is being updated in multiple places in other activities.
For example there could be the possibility to make new entries by starting a new Activity and saving it there.In that case I would intuitively start that activity with startActivityForResult() and receive the changes in the onActivityResult() method in the MainActivity.
But lets say deeper inside the application, there is the possibility to delete all entries. Now how do we notify the MainActivity about this change in the dataset? I thought about setting a flag of some kind and clearing it after the content has been updated in the MainActivity. But somehow using global variables does not really follow the principle of proper encapsulation, does it?
Sorry for this vague question, but I find it quite hard to properly handle information flow in Android in a elegant manner, so here we are.
How about a local broadcast? You can find the idea of broadcast in this document. You need local broadcast and it is preferred if you want to pass data within your app only.
Android apps can send or receive broadcast messages from the Android system and other Android apps, similar to the publish-subscribe design pattern. These broadcasts are sent when an event of interest occurs. For example, the Android system sends broadcasts when various system events occur, such as when the system boots up or the device starts charging. Apps can also send custom broadcasts, for example, to notify other apps of something that they might be interested in (for example, some new data has been downloaded).
You can use Handler to pass the Message in Activity and then You have to update RecyclerView. Like,
1) In Activity.
public static Handler mHandler = new Handler(new Handler.Callback() {
#Override
public boolean handleMessage(Message msg) {
if(msg.what == 1223){
//update RecyclerView
}
return false;
}
});
2) pass message When you want to update RecyclerView
Message msg = new Message();
msg.what = 1223;
Activity1.mHandler.sendMessage(msg);
You can use EventBus to handle it.
Define a class for your event
public static class MyEvent {
int event;
/* define your fields */
}
And prepare your subscriber in main activity
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMyEvent(MyEvent myEvent) {
switch(myEvent.event) {
/* Do what you need */
}
};
Now where you need to make change, call your subscriber like this:
MyEvent myEvent = new MyEvent();
myEvent.event = 1;
EventBus.getDefault().post(myEvent);
You can read more about EventBus in here
If you were using RxJava2, RxAndroid. Then you could try this.
Create a Bus:
public final class RxBus {
private static final BehaviorSubject<Object> behaviorSubject = BehaviorSubject.create();
public static BehaviorSubject<Object> getSubject() {
return behaviorSubject;
}
}
In your WaitingActivity where you want to receive data(where you want not to use onActivityResult in your case)
Disposable disposable = RxBus.getSubject().
subscribeWith(new DisposableObserver<Object>() {
#Override
public void onNext(Object o) {
if (o instanceof DataObject) {
//((DataObject) o).getValue();
}
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
});
In your activity where you want to send data
RxBus.getSubject().onNext(dataObject);
startActivity(new Intent(CurrentActivity.class, WaitingActivity.class));
Finally don't forget to dispose your disposable to avoid memory leaks in your WaitingActivity
#Override
protected void onDestroy() {
super.onDestroy();
disposable.dispose();
}
Your data should be separate from view, in model. If some other activity changes data ideally recycler view must be updated from there. So no matter which activity does what, when you refresh data on load or resume of your recycler view you will always get correct results.

ViewModel method name convention

What is the best way to name methods in the ViewModel classes? Based on its action/behavior or the lifecycle of the Activity/Fragment?
For example:
Methods named by its action
override fun onResume() {
super.onResume()
viewModel.connect()
}
override fun onPause() {
super.onPause()
viewModel.disconnect()
}
override fun onItemCheckedChanged(task: Task, value: Boolean) =
viewModel.updateTaskStatus(task, value)
Methods named by Android lifecycle
override fun onResume() {
super.onResume()
viewModel.onResume()
}
override fun onPause() {
super.onPause()
viewModel.onPause()
}
override fun onItemCheckedChanged(task: Task, value: Boolean) =
viewModel.onItemCheckedChanged(task, value)
There are several examples in the internet and the two approaches are used.
In my opinion, the methods should be related to the lifecycle, in this way the View does not need to know the logic behind the ViewModel, it just need to know that a lifecycle method need to be called.
What is the best approach?
There is not a correct way, as long as the code is clean and easy to read/understand. But if you look at the examples Android give, they show methods similar to the ones you posted.
1) One of the ways is to have an object with the methods named by Android lifecycle (Which you mentioned).
class MyLocationListener {
public MyLocationListener(Context context, Callback callback) {
// ...
}
void start() {
// connect to system location service
}
void stop() {
// disconnect from system location service
}
}
Each function is manually called within the lifecycle owner like so:
#Override
public void onStart() {
super.onStart();
myLocationListener.start();
// manage other components that need to respond
// to the activity lifecycle
}
#Override
public void onStop() {
super.onStop();
myLocationListener.stop();
// manage other components that need to respond
// to the activity lifecycle
}
2) However, if you want to name the methods by their actions, you could accompany them methods with the OnLifecycleEvent annotation, which was excitingly brought to us in Android Jetpack! So for example:
public class MyLocationListener implements LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void connectListener() {
...
}
#OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void disconnectListener() {
...
}
}
Now these methods are called automatically with the help of a LifecycleObserver which can observe a LifecycleOwner:
myLifecycleOwner.getLifecycle().addObserver(new MyLocationListener());
The LifecycleOwner is typically an Activity or Fragment. Up to you which one you choose, however my preferred is the LifecycleObserver as it requires less code, which I think makes it look cleaner.
If your interested in good Android practices and some tips to help you along the way, there are a few good pages I would recommend:
- Android best practices
- Android tips & tricks
- Android must have libraries
IMO the best approach is to use the name of the lifecycle for these reasons:
It makes the view (fragment) dumber. In effect, as soon as you name the function connect() the view is making the decision (logic) to connect.
What happens if now you want to do something else in onResume(), like refresh data? Do you add another function called refreshData() and call it after connect()? Or do you call it before connect() because you know that the data must be refreshed before you can connect. Oops you've added more implicit logic to your view, that a test of just the viewmodel will fail to catch. Better to just call fun onResume() { refreshData(); connect(); }
In general you should name all your view events along the lines of "something happened to me". Eg. didPressDowload() orderConfirmed() etc. If you name it like orderPizza() instead, that's like a command and if you want to fail the order like fun orderPizza() { if (orderEmpty) return ....} then that function no longer is doing what it says in the name.
It's small semantics sometimes, but other times it really saves you errors and getting confused about how to keep logic in the viewmodel only.
See https://redux.js.org/style-guide/style-guide#model-actions-as-events-not-setters for similar advice from the web world.

Android ViewModel recreated on screen rotation

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)

Android ViewModel call Activity methods

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);

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'

Categories

Resources