Where to place Toasts, Snackbars etc. in era of Android Architecture Components - android

What is the recommendation about placing Toasts, Snackbars in Activity vs. Fragment in the era of ViewModel and one activity multiple fragments concept.
Its clear that things like
new Intents
Fragment transactions/fragment setup
permission requests
presenting navigational components (drawer, viewpager)
should be handled by activity but what about Toasts/Snackbars?
There are so many SO questions about how to show Toasts or Snackbars in Fragment and how + when to use getContext() that I tend to let the activity handle all showing messages.
With ViewModel and SingleLiveEvent its quite easy to have a sharedViewModel that will be observed by activity and fragments and when needed a fragment dispatchs an event that notifies the activity to show a message.
So what do you think is best practice/recommended while having resposibility and separation of concerns in mind?

Taking a cue from Google's codelabs on basic architecture components, it seems better to let fragments/activities react to changes in ViewModel.
From the Google link above, it thus means that one of the most efficient methods is to Observe() a LiveData property of a ViewModel object from your Activity

it depends. Probably you can always replace toasts/snackbars with some UI parts (TextViews, Buttons) or Dialogs / DialogFragments.
it really depends on your app architecture, but I guess you can design some dependency: activity- or fragment-scoped, so you can inject it as abstraction for your ViewModel and use like you want.
IMHO if you deal with Toasts, you can design MessageShower at activty/application scope and reuse it everywhere(dealing with single instance of toast), but Snackbars needs View to be shown(so they need lifecycle, and it is more easier to make memory leak).
interface MessageShower{
void showMessage(String message);
}
class LongToastMessageShower implements MessageShower{
private Toast toast;
public LongToastMessageShower(Context appContext){
this.toast = Toast.makeText(appContext,"",Toast.LENGTH_LONG);
}
#Override
public void showMessage(String message){
toast.cancel();
toast.setText(message);
toast.show();
}
}

You can create BaseActivity and BaseFragment where you create all common methods like Toast, Snackbar etc which is extended in YourActivity and YourFragment.

use AndroidViewModel()
e.g
class LoginViewModel(application: Application) : AndroidViewModel(application) {
private val context = getApplication<Application>().applicationContext
//use that context when toasting in your viewModel
}
for snackbar
if its a button in your xml use this attribute and value
android:onClick="#{() -> login.signIn(signIn)}"
signIn inside the brackets in that context refers to the id of your button on which you are setting the onClick attribute
Remember that login before .signIn refers to your variable name
In your viewModel
fun signIn(view: View){
//use view to display your snack bar
}

Related

MVVM - How to cleanly incorporate AlertDialog?

I have a FragmentOne which contains the MVVM pattern. The Fragment has it's own ViewModel, FragmentOneViewModel. I am unsure on how to cleanly incorporate an AlertDialog in my case.
In the Fragment it contains a RecyclerViewAdapter, with each row that contains a Button to create an AlertDialog. Do I need a ViewModel for the AlertDialog?
Or can I use an interface callback and implement it on my Fragment, which will be called when the user clicks on buttons in the AlertDialog. And then use that input to call the Fragment's ViewModel?:
public class ActivateUserDialog extends AlertDialog {
//Should I use callback methods?
private Context mContext;
public interface DialogOnClickListener{
void onCancelBtnClick();
void onConfirmBtnClick(String amount);
}
public ActivateUserDialog(#NonNull Context context) {
super(context);
this.mContext = context;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_activate);
ButterKnife.bind(this);
}
#OnClick(R.id.btn_cancel)
public void onBtnDismiss() {
this.dismiss();
}
#OnClick(R.id.btn_confirm)
public void onBtnConfirm() {
//Call the fragment
MainActivity activity = (MainActivity) mContext;
MembersFragment fragment = (MembersFragment) .... get the fragment;
fragment.onConfirmBtnClick(amount);
}
}
And then calling the viewmodel from the callback method:
//Fragment class:
public class MembersFragment extends Fragment implements ActivateUserDialog.DialogOnClickListener
#Override
public void onConfirmBtnClick(String amount){
mViewModel.activateUser(amount);
}
Is this a clean way to do it?
I don't think there's a single answer to it and would say it's opinionated. Both would be valid in my opinion.
You also need to consider that architecture components don't work "out of the box" with AlertDialogs, so even if you want a VM for it you will have to retrieve it through the fragment/activity (basically a ViewModelProvider) or use some other solution like DI.
You would also have to manage the lifecycle of the AlertDialog to gain some of the benefits of the ViewModel.
Here are possible solutions:
Consider the AlertDialog as part of the View of FragmentOne and communicate with it via callbacks. In the implementation communicate with FragmentOneViewModel.
Use a DialogFragment with a ViewModel instead. Since DialogFragment is basically just a Fragment you get all the benefits of the architecture components ViewModel. You can either share FragmentOneViewModel between the two or create a new ViewModel dedicated to the dialog. Whether to share or create a new one is up to your needs but I would consider the complexity of the Dialog and whether it should be stand-alone. If you decide to share the ViewModel, be careful which ViewModelProvider you use to make sure they are actually shared and not a different instance.
As for communicating back from the AlertDialog, I would suggest using an interface with callbacks instead of passing Context and casting directly.
You can accept an implementation in the constructor.
You want to use a dialog box on some click in recycler-view adapter which is based on MVVM pattern and want some callback on adapter after dialog box action right?
Yes this is what I am doing in my code above, but I don't know if
this is the best practice way to do it with Dialog – DIRTY DAVE
As I think its a good way easy to understand and maintainable. However, dialog box is part of the UI and so interaction with it. So it should be in the Fragment. The fragment then interacting with the view-model is a reliable way to do things as that's the main home for your logic.
As the MVVM design pattern is similar to the well known MVC pattern in that the M (Model) and V (View) are relatively the same. The only difference resides between the C (Controller) and the VM (View Model).
Model
Represents the Data + State + Business logic. It is not tied to the view nor to the controller, which makes it reusable in many contexts.
View
Binds to observable variables and actions exposed by the View Model. It is possible for multiple views to bind to a single View Model.
View Model
Responsible for wrapping the model and preparing observable data needed by the view. It also provides hooks for the view to pass events to the model. An important thing to keep in mind is that the View Model is not tied to the view.
Source

Android ViewModel for a custom view

I would like to refactor my Custom View to use android architecture components. However, I see that
ViewModelProviders.of(...)
takes only Activity or fragment. Any idea how to make it work? Should I use fragment instead of Custom View?
It is possible to get obtain a ViewModel instance in View, although it's not recommended. As per this post:
While it is easy enough to obtain the ViewModels inside an Activity or a Fragment, it is not straightforward to obtain this instance inside a View. The main reason behind this is because Views are supposed to be independent of all processing and even if all your logic is inside a ViewModel, the fact that you are accessing that ViewModel inside the View makes it reliant on something that it shouldn't. The recommended way of controlling a View is to pass parameters to it based on the state of the ViewModel from the Fragment or the Activity.
The point is to try to get an Activity from the context:
override val activity: FragmentActivity by lazy {
try {
context as FragmentActivity
} catch (exception: ClassCastException) {
throw ClassCastException("Please ensure that the provided Context is a valid FragmentActivity")
}
}
override var viewModel = ViewModelProvider(activity).get(SharedViewModel::class.java)
As mentioned thought, I'd try to avoid this approach if possible.

Share ViewModel between fragments that are in different Activity

I have a ViewModel named SharedViewModel:
public class SharedViewModel<T> extends ViewModel {
private final MutableLiveData<T> selected = new MutableLiveData<>();
public void select(T item) {
selected.setValue(item);
}
public LiveData<T> getSelected() {
return selected;
}
}
I've implemented it based on SharedViewModel example on the Google's Arch ViewModel reference page:
https://developer.android.com/topic/libraries/architecture/viewmodel.html#sharing_data_between_fragments
It is very common that two or more fragments in an activity need to communicate with each other. This is never trivial as both
fragments need to define some interface description and the owner
activity must bind the two together. Moreover, both fragments must
handle the case where the other fragment is not yet created or not
visible.
I have two fragments, called ListFragment and DetailFragment.
Until now I used these two fragments inside an activity called MasterActivity, and everything worked well.
I got the ViewModel in ListFragment, selected the value to use it on DetailFragment.
mStepSelectorViewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
However, now, in certain cases, I need that ListFragment (a layout to a different device configuration) will be added to a different activity, called DetailActivity. Is there a way to do that similarly to the above example?
A little late but you can accomplish this using a shared ViewModelStore. Fragments and activities implement the ViewModelStoreOwner interface. In those cases fragments have a store per instance and activities save it in a static member (I guess so it can survive configuration changes).
Getting back to the shared ViewModelStore, let say for example that you want it to be your Application instance. You need your application to implement ViewModelStoreOwner.
class MyApp: Application(), ViewModelStoreOwner {
private val appViewModelStore: ViewModelStore by lazy {
ViewModelStore()
}
override fun getViewModelStore(): ViewModelStore {
return appViewModelStore
}
}
Then in the cases when you know that you need to share ViewModels between activity boundaries you do something like this.
val viewModel = ViewModelProvider(myApp, viewModelFactory).get(CustomViewModel::class.java)
So now it will use the Store defined in your app. That way you can share ViewModels.
Very important. Because in this example the ViewModels live in your application instance they won't be destroyed when the fragment/activity that uses them gets destroyed. So you will have to link them to the lifecycle of the last fragment/activity that will use them, or manually destroy them.
Well, I created a library for this purpose named Vita, You can share ViewModels between activities and even fragments with different host activity:
val myViewModel = vita.with(VitaOwner.Multiple(this)).getViewModel<MyViewModel>()
The created ViewModel in this way stay alive until its last LifeCycleOwner is destroyed.
Also you can create ViewModels with application scope:
val myViewModel = vita.with(VitaOwner.None).getViewModel<MyViewModel>()
And this type of ViewModel will be cleared when user closes app
Give it a try and kindly let me know your feedback:
https://github.com/FarshadTahmasbi/Vita
you can use factory to make viewmodel and this factor will return single object of view model.. As:
class ViewModelFactory() : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
if (modelClass.isAssignableFrom(UserProfileViewModel::class.java)) {
val key = "UserProfileViewModel"
if(hashMapViewModel.containsKey(key)){
return getViewModel(key) as T
} else {
addViewModel(key, UserProfileViewModel())
return getViewModel(key) as T
}
}
throw IllegalArgumentException("Unknown ViewModel class")
}
companion object {
val hashMapViewModel = HashMap<String, ViewModel>()
fun addViewModel(key: String, viewModel: ViewModel){
hashMapViewModel.put(key, viewModel)
}
fun getViewModel(key: String): ViewModel? {
return hashMapViewModel[key]
}
}
}
In Activity:
viewModelFactory = Injection.provideViewModelFactory(this)
// Initialize Product View Model
userViewModel = ViewModelProviders.of(this, viewModelFactory).get(
UserProfileViewModel::class.java)`
This will provide only single object of UserProfileViewModel which you can share between Activities.
I think we still get confused with the MVVM framework on Android.
For another activity, do not get confused because it must necessarily be the same, why?
This makes sense if it has the same logic (even if the logic could still be abstract in other useful classes), or if the view in the XML is almost identical.
Let's take a quick example:
I create a ViewModel called vmA, and an activity called A and I need the user's data, I will go to insert the repository in vmA of the User.
Now, I need another activity that needs to read user data,
I create another ViewModel called vmB and in it I will call the user repository.
As described, the repository is always the same.
Another way already suggested is to create N instances of the same ViewModel with the implementation of the Factory.
If you want a ViewModel that is shared by all your activities (as opposed to some),
then why not store what you want stored in that ViewModel
inside your Application class?
The trend presented at the last Google I/O seems to be to abandon the concept of Activities in favor of single-activity apps that have a lot of Fragments.
ViewModels are the way to remove the great number of interfaces the activity of an interface formerly had to implement.
Thus this aproach no longer makes for giant and unmaintainable activities.
Here's a link
Hope it helps you. O(∩_∩)O~
In addition:
1) The inspiration for the code came from smart pointer in c++.
2) It will be auto cleared when no activities or fragments references ShareViewModel.
The ShareViewModel # onShareCleared() function will be called at the same time!
You don't need to destroy them manually!
3) If you use dagger2 to inject the ViewModelFactory for share the viewmodel
between two activities (maybe three), Here's sample

Android MVP WeakRefrence

In mvp we save that reference of an activity in a weak reference.
WeakReference<Activity> view = new WeakReference<Activity>(activity);
If we lose the reference. can we get it back?
If you lose the reference to your Activity it means the activity was garbage collected and it doesn't exist anymore. You can't get back what doesn't exist.
Ex. If this happens because of configuration change it means a new activity was created.
You need a way to attach the newly created view to the same presenter.
If you are looking for libraries to help you, take a look at mosby and nucleus.
I don't think you should be saving a reference to an Activity in MVP at all - doesn't matter if it's hard or weak!
I'm assuming you're storing this reference in the Presenter. To really decouple the layers you should create an interface that describes your View (Activity) and use it instead of the activity.
So you'd do:
public interface LoginView {
displayUsernameError(String error);
displayPasswordError(String error);
openMainScreen();
}
Your Activity should implement the interface from above.
public class LoginActivity implements LoginView {
...
}
In your presenter you'd have:
class LoginPresenter {
private LoginView mView;
public LoginPresenter(LoginView view) {
mView = view;
}
public onLoginButtonClicked(String username, char[] password) {
...
mView.openMainScreen();
}
}
Immediate benefits of doing this:
The different layers are really decoupled. You can change your Activity (say you decide to use Fragments instead) without touching your Presenter.
Your presenter is fully testable using JUnit only! No need to use anything fancy to verify your interactions are correct, just plain Mockito to mock the LoginView.
One other point to note - are you sure you want your Presenter to outlive your View? There are some situations when that can't be avoided, but in most cases they have the same life span - when the View is destroyed the Presenter should be as well.
How did you set the reference in the first place?
You should be setting it with a setter method in Activity's onCreate.
This "setter" method is often called "attach" or "bind".
fun attach(view: View) {
this.view = view
}
So when new Activity is created due to configuration change, it will set itself to the presenter again. Note that you might be dealing with a new instance of the presenter too. However, based on your question I believe you want to attach the newly created activity to the same instance of presenter. If you got your presenter scoped right, this will work for both cases :)

Is it preferable to use Activity.onAttachFragment or Fragment.onAttach to communicate between an Activity and a nested fragment?

The Android documentation suggests that to communicate from an activity to a hosted fragment, the fragment can define a callback interface and require that the host activity implement it. The basic pattern involves implementing onAttach in your fragment, and casting the activity to a callback inteface. See http://developer.android.com/guide/components/fragments.html#CommunicatingWithActivity.
Here's an example of providing a fragment some initialization data, as well as listening for a navigation callback.
public class HostActivity extends Activity implements FragmentHost {
#Override
UiModel getUiModel() {
return mUiModel;
}
#Override
FragmentNavListener getNavListener() {
return mNavListener;
}
...
}
public class HostedFragment extends Fragment {
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof FragmentHost) {
FragmentHost host = (FragmentHost) activity;
setUiModel(host.getUiModel());
setNavListener(host.getFragmentNavListener());
}
}
...
}
Compare this to using onAttachFragment in the host activity to explicitly initialize the fragment:
public class HostActivity extends Activity {
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment instanceof HostedFragment) {
HostedFragment hostedFragment = ((HostFragment) fragment);
hostedFragment.setUiModel(mUiModel);
hostedFragment.setNavListener(mNavListener);
}
}
...
}
To me, it seems like the first pattern has some drawbacks:
It makes the fragment harder to use from different activities, since
since all of those activities must implement the required interface. I can imagine cases where a given fragment instance doesn't require being fully configured by the host activity, yet all potential host activities would need to implement the host interface.
It makes the code slightly harder to follow for someone unfamiliar with the pattern being used. Initializing the fragment in onFragmentAttached seems easier to follow, since the initialization code lives in the same class that creates the fragment.
Unit testing using a library like Robolectric becomes harder, since when calling onAttach, you must now implement FragmentHost rather than just calling onAttach(new Activity().
For those of you who've done activity to fragment communication, what pattern do you find preferable, and why? Are there drawbacks to using onAttachFragment from the host activity?
I cant speak personally with respect to testing but there is alternatives to fragment / activity callback interface communication.
For example you can use a event bus to decouple the fragments and your activity. An excellent event bus can be found here:
Otto - An event Bus by Square
It is actively being developed by some very talented engineers at Square.
You can also use the LocalBroadcastManager that is packaged in the Android Support Library.
LocalBroadcastManager
Eric Burke from square has a presentation where he mentions both which can be found here:
Android App Anatomy
Update : As per the latest guidelines we are supposed to use a sharedViewModel class to communicate between fragments and activities.
However if you don't use viewModels and use callbacks, you should still consider removing onAttachFragment callback as it is deprecated now.
Instead on onAttachFragment, the suggested way is to add a listener to your fragmentManager who is associated with that transaction using addFragmentOnAttachListener.
See the example below:
childFragmentManager.addFragmentOnAttachListener { _, fragment ->
if (fragment is RetryDialogFragment) {
//Do your work here.
}
}
You should also be mindful of where you add this listener. You most probably want to add this listener before you execute the fragment transaction and should make sure that you are not adding the listener more than once.
I have used the Fragment.onAttach(...) pattern in my last project. I see two advantages:
you can check early that the hosting activity implements the required interface and throw an exception if not
there's less risk of holding onto a reference of the hosting context after the fragment has been detached
In order to take advantage of 2., you must not store references to UiModel and NavListener, as you do in your code sample. Instead, whenever you want to interact with these instances, you should use code like ((FragmentHost) getActivity).getNavListener().onNav(this), or alternatively ((FragmentHost) getActivity).onNav(this). You could store the fragment host in a field which you set to null in onDetach(...) as a middle ground approach, if you want to avoid the constant casting.
I agree that initializing a fragment from the activity that creates it seems more intuitive.
Having said all that, I'm going to skip fragments altogether in my current project. The following post reflects the lessons learned from my last one pretty well: https://corner.squareup.com/2014/10/advocating-against-android-fragments.html

Categories

Resources