Call View from View on MVP - android

For example i have a button, if user click the button, it just call finish(). Shall i tell the presenter the button is get clicked? :
//--------------HomeView.kt--------------
var presenter: HomePresenter? = null
override fun onCreate(...) {
btBack.setOnClickListener {
presenter.onBackPress()
}
}
private fun onBackPress() {
finish()
}
//--------------HomePresenter.kt--------------
var view : HomeView? = null
private fun onBackPress() {
view?.onBackPress()
}
or can i simplified it just call finish() in its onClickListener()? :
//--------------HomeView.kt--------------
override fun onCreate(...) {
btBack.setOnClickListener {
finish()
}
}

Unless you need some logic to be executed before finishing the activity, tell the presenter, otherwise I do not see much any advantage do this

As with almost all architectural questions, this comes down to personal preferences and how much you like your code to be clean.
Personally, I would suggest to always call the presenter for each interaction on the view (even if they are just one line calls).
this allows you to move all the logic out of the view and into the presenter (since all interactions on the view just pass the call directly to the presenter, without any attached logic)
it makes your whole app much more testable, since you can mock the view from the presenter and test the logic right there
it makes the architecture of your app cleaner, since you always know that calls get send to the presenter for every single interaction
it makes your code more future proof, since you can add new logic right there in the presenter call and do not have to move code around first (or worse: just be lazy and add the logic directly in the view)
Of course I have to admit that it adds quite a lot of boilerplate code, but I think this is worth it.

Related

How to run CoroutineScope in RecyclerViewCursorAdapter on ViewHolder inside bindCursor{ }

Note: I had implemented Coroutine Single Scope in RecyclerViewCursorAdapter but the app getting too slow when I change it to runBlocking it's working fine
Is there any way to execute CoroutineScope in ViewHolder inside bindCursor{ } on every scroll without impacting on performance ?
I need to execute multiple queries from the database on every scroll and update the UI accordingly.
I am pretty sure, there is a better way of doing what you are doing than to do it in adapter. I don't think that is correct, maybe you can use a callback via an interface or something.
But to return to your question, you should be easily be able to do this using lifeCycleScope
A LifecycleScope is defined for each Lifecycle object. Any coroutine launched in this scope is canceled when the Lifecycle is destroyed.
Since you have not mentioned whether it is an activity or an fragment that you are using, but the logic and functionality remains same for both. I am going to give you an example for Fragment
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
// Do stuff here
}
}
}
Just like that you can launch coroutines and it will be handled as per the lifecycle, you may need to pass the viewLifeCycleOwner to your Fragment, although as I said that is not recommended, you are better using an interface of sort and then computing in Fragment itself, but you can get the idea.

Sharing display state between views in MVVM without relying on singletons

My app has several long processes that are user-initiated by different fragments and I'd like to display a progress dialog while they are being accomplished. I'd like this progress dialog to be displayed across several fragments so that bottom navigation is still enabled, so that the user still has a sense of agency, but they can't take any actually harmful actions otherwise. Most of my "main" fragments are blocked in this way, but others such as the settings and help fragments don't need to be.
My current solution is less than ideal. Actions are user-initiated in the fragment, but the activity becomes responsible for actually completing them, so that it can overlay a progress dialog over every fragment that it needs to. I'd much rather have the fragments be responsible for their own tasks.
The obvious separation of concerns is becoming pretty essential, as the size of the activity VM has become massive, and too much of the business logic is in that class (and is then delegated to the model appropriately).
e.g.
class MyFragment : Fragment() {
// MyActivity will implement this interface
interface NetworkProcess {
fun start()
}
// start() is called on a button click or something similar
}
class MyActivity : AppCompatActivity(), MyFragment.NetworkProcess {
override fun onCreate(savedInstanceBundle: Bundle?) {
// Observe state from VM layer
// Observer updates progress dialog
}
override fun start() {
// Pass action to VM layer
}
}
One solution I have tried is to have the interface only used for display state, but then if the fragment is navigated away from it is unable to call the interface and continue updating the dialog.
e.g.
class MyFragment : Fragment() {
// MyActivity will implement this interface
interface NetworkProcessDialog {
fun update(text: Int)
fun stop()
}
override fun onActivityCreated() {
// Observe state from viewmodel
// Set listener on a button send action to viewmodel to start process
}
}
class MyActivity : AppCompatActivity(), MyFragment.NetworkProcessDialog {
override fun updateDialog(text: Int) {
// Make dialog visible if not and show update
}
override fun stopDialog() {
// Make dialog invisible
}
}
Another solution I've thought of is to push the view state into the model layer. It seems though that this would necessitate a singleton (and a bunch concurrency protection work) so that the viewmodels can't start overwriting the state as it's in the process of accomplishing one of these long running tasks. This is currently avoided automatically since there is only ever one activity VM handling all the work.
So, is it possible to have this kind of separation of concerns without relying on singletons in the model layer?
I wouldn't have the Activity (nor the Fragments) own such responsibility. They are policyDelegates (name used by Google) for things you cannot get rid of (you NEED one or the other for your app to function). So they should only do what they know better:
Handle Lifecycle (can't get around this)
Inflate Views and display them
Save/Restore their states (when possible)
Interface between fragments (if needed)
Receive state updates and reflect that in the corresponding views
etc.
So what would I do?
Have a LongProcessesManager (name is up 2 you), that is capable of starting, stopping, and managing N number of "processes". It must also offer ways to indicate progress of said process.
Have a ProcessFragment that is capable of using said manager to initiate, and receive updates for a process (and it can act on said results, like update a progress bar).
If you need to display a GLOBAL progress, you can do so too, you activity can host more than 1 fragment, so there's nothing stopping you from putting a second fragment on screen that doesn't disappear while there's progress (like a music player for example) or whatever, I don't know your UI :)
Consider moving the management of said Manager to a Service that can become Foreground if that is a requirement (user hitting "Home" or moving away from your app to pick a phone call or check instagram while they wait for your LONG running processes).
Use DependencyInjection (Dagger, Koin, you pick) so you have a single instance of this manager, that gets injected in all the fragments (or Shared ViewModels if you want).
This is just a list of a few things I'd do out of the box if I were tasked with something like this.

RecyclerView click listener, why this simpler approach is not used more often

New to Android development here. I am creating a RecyclerView that presents a new activity when certain items are clicked. I searched online and all of them would define a click listener interface, pass the listener to view holders, and ultimately call startActivity from the main activity.
Alternatively, I came up with the following. Since nobody is using this approach, what's wrong with it? It is just a few lines of code in the adapter class and seems to work equally well
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is certainViewHolder -> {
holder.itemView.setOnClickListener {
val intent = Intent(holder.itemView.context, DetailedActivity::class.java)
holder.itemView.context.startActivity(intent)
}
}
else -> ...
}
}
Nothing wrong with your approach and many people use it. Usually first approach used most of time because calling method using interface is good practice. When we create a project using certain architecture like MVP or MVVM or any other we need to call all method using interface. Currently in your project you just need to start a activity but many times we to perform longer and difficult task so it better to perform in relate activity. Through we can collect all method in same place.

How to connect View and ViewModel with respect to screen rotation?

One promise of the ViewModel is, that it survives cases like rotation of the screen. I still try to figure out how to organise this in practice.
On certain events of the model the View should update. There are two major options:
The ViewModel updates the View.
The View observes the ViewModel and updates itself.
In the first case the ViewModel needs a link to the View. I could inject the View into the ViewModel, yet my feeling is it would be better to inject the VieModel into the View.
What is the better style to join them?
Then after rotation the onCreate() method is called again triggering initialisations of the ViewModel a second time. I need to check for this else I am in danger to register listeners to the actual model twice and thrice and similar issues. I may even need to clean up relations to the old view first.
This checking feels kind of unclean. I would expect a dedicated API for this in the ViewModel, if this would be a standard practice. Without I have the feeling to be on the wrong track.
What are good patterns to deal with this in a clean standard way?
Soo.. You don't really have to connect the ViewModel and the Activity/Fragment "with respect to screen rotation", you get that for free - that's one of the perks.
The official documentation is really good.
You connect a ViewModel to your view in onCreate() by something like
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
updateUI()
});
}
}
And while it is true as you say that orientation change will trigger onCreate() again, it's not true that this will create a new ViewModel. The MyViewModel is only created the first time around in onCreate. Re-created activities receive the same MyViewModel instance created by the first activity. This is even true for different fragments/activities referencing the same ViewModel.
You should never ever inject a view into the ViewModel. It's the equivalent of drowning puppies. If you need a context in the ViewModel, extend AndroidViewModel instead (and pass it the Application).
What you do is that you create a ViewModel that holds all state. And handles fetching data from network or disk or what not. All that is not UI related goes in to the ViewModel (as a rule of thumb). All view updating stuff goes into the activity/fragment.
A ViewModel for the example above might look like
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<Users>>();
loadUsers();
}
return users;
}
private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
This often means that click events should probably be passed down into the ViewModel so that it can massage the data. And the view will just react to the updated (massaged) data.

MVVM pattern and startActivity

I recently decided to have a closer look at the new Android Architecture Components that Google released, especially using their ViewModel lifecycle-aware class to a MVVM architecture, and LiveData.
As long as I'm dealing with a single Activity, or a single Fragment, everything is fine.
However, I can't find a nice solution to handle Activity switching.
Say, for the sake of a short example, that Activity A has a button to launch Activity B.
Where would the startActivity() be handled?
Following the MVVM pattern, the logic of the clickListener should be in the ViewModel. However, we want to avoid having references to the Activity in there. So passing the context to the ViewModel is not an option.
I narrowed down a couple of options that seem "OK", but was not able to find any proper answer of "here's how to do it.".
Option 1 : Have an enum in the ViewModel with values mapping to possible routing (ACTIVITY_B, ACTIVITY_C). Couple this with a LiveData.
The activity would observe this LiveData, and when the ViewModel decides that ACTIVITY_C should be launched, it'd just postValue(ACTIVITY_C). Activity can then call startActivity() normally.
Option 2 : The regular interface pattern. Same principle as option 1, but Activity would implement the interface. I feel a bit more coupling with this though.
Option 3 : Messaging option, such as Otto or similar. ViewModel sends a Broadcast, Activity picks it up and launches what it has to. Only problem with this solution is that, by default, you should put the register/unregister of that Broadcast inside the ViewModel. So doesn't help.
Option 4 : Having a big Routing class, somewhere, as singleton or similar, that could be called to dispatch relevant routing to any activity. Eventually via interface? So every activity (or a BaseActivity) would implement
IRouting { void requestLaunchActivity(ACTIVITY_B); }
This method just worries me a bit when your app starts having a lot of fragments/activities (because the Routing class would become humongous)
So that's it. That's my question. How do you guys handle this?
Do you go with an option that I didn't think of?
What option do you consider the most relevant and why?
What is the recommended Google approach?
PS : Links that didn't get me anywhere
1 - Android ViewModel call Activity methods
2 - How to start an activity from a plain non-activity java class?
NSimon, its great that you start using AAC.
I wrote a issue in the aac's-github before about that.
There are several ways doing that.
One solution would be using a
WeakReference to a NavigationController which holds the Context of the Activity. This is a common used pattern for handling context-bound stuff inside a ViewModel.
I highly decline this for several reasons. First: that usually means that you have to keep a reference to your NavigationController which fixes the context leak, but doesnt solve the architecture at all.
The best way (in my oppinion) is using LiveData which is lifecycle aware and can do all the wanted stuff.
Example:
class YourVm : ViewModel() {
val uiEventLiveData = SingleLiveData<Pair<YourModel, Int>>()
fun onClick(item: YourModel) {
uiEventLiveData.value = item to 3 // can be predefined values
}
}
After that you can listen inside your view for changes.
class YourFragmentOrActivity {
//assign your vm whatever
override fun onActivityCreated(savedInstanceState: Bundle?) {
var context = this
yourVm.uiEventLiveData.observe(this, Observer {
when (it?.second) {
1 -> { context.startActivity( ... ) }
2 -> { .. }
}
})
}
}
Take care that ive used a modified MutableLiveData, because else it will always emit the latest result for new Observers which leads to bad behaviour. For example if you change activity and go back it will end in a loop.
class SingleLiveData<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, Observer { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
#MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#MainThread
fun call() {
value = null
}
companion object {
private val TAG = "SingleLiveData"
}
}
Why is that attempt better then using WeakReferences, Interfaces, or any other solution?
Because this event split UI logic with business logic. Its also possible to have multiple observers. It cares about the lifecycle. It doesnt leak anything.
You could also solve it by using RxJava instead of LiveData by using a PublishSubject. (addTo requires RxKotlin)
Take care about not leaking a subscription by releasing it in onStop().
class YourVm : ViewModel() {
var subject : PublishSubject<YourItem> = PublishSubject.create();
}
class YourFragmentOrActivityOrWhatever {
var composite = CompositeDisposable()
onStart() {
YourVm.subject
.subscribe( { Log.d("...", "Event emitted $it") }, { error("Error occured $it") })
.addTo(compositeDisposable)
}
onStop() {
compositeDisposable.clear()
}
}
Also take care that a ViewModel is bound to an Activity OR a Fragment. You can't share a ViewModel between multiple Activities since this would break the "Livecycle-Awareness".
If you need that persist your data by using a database like room or share the data using parcels.
You should call startActivity from activity, not from viewmodel. If you want to open it from viewmodel, you need to create livedata in viewmodel with some navigation parameter and observe on livedata inside the activity
You can extend your ViewModel from AndroidViewModel, which has the application reference, and start the activity using this context.

Categories

Resources