I'm trying to create an app which will use MVVM architecture and there's one thing I quite don't understand.
Official Android docs say that's not a good idea to reference activity context in ViewModel's (as ViewModel may outlive activity) so I've started to wonder about usecase when I want to execute some action when my activity is resumed.
I know ViewModel's shouldn't do business logic themselves but even if I use some service class (let's say GPSService which has to start and pauseeach time activity is resumed on paused), and inside this service I react to activity onResume (using Lifecycle observer) I will still reference this activity from ViewModel as I'm referencing service which holds reference to activity being observed, this may cause activity leak (correct me if I'm wrong).
So my question is, how to react to activity or fragment lifecycle in MVVM architecture?
If you need to have a ViewModel be lifecycle aware, then you can have it implement LifeCycleObserver and override life cycle events as necessary. Example,
public class MyModel extends ViewModel implements
LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
protected void onLifeCycleStop() {
// do something
}
}
In the activity or fragment then you can add the view model to the activity life cycle owner.
public class MyActivity extends AppCompatActivity {
protected MyModel mMyModel;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMyModel = ViewModelProviders
.of(this)
.get(MyModel.class);
getLifecycle().addObserver(mMyModel);
}
}
I know ViewModel's shouldn't do business logic themselves
Yes, you're right. ViewModel should not contain business logic but
it should contain UI related logic. So basically, API calls or Some
location related stuffs should be avoided in ViewModel logic.
So what if you wanna make some scenario which can react to any activity lifecycle? I'll suggest you to use LifecycleObserver.
Why?, Because LifecycleObserver will provide you callbacks once it's LifecycleOwner will change it's state.
What is LifecycleOwner here? In our case it may be Activity/Fragment.
So, how you can achieve this?
Let's say you want to make location requests during resume & pause period of any activity.
So, for that you can create a class called LocationUpdates as LifecycleObserver like below:
class LocationUpdates : LifecycleObserver {
constructor(){
// some basic location related initialization here
}
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun connectListener() {
// this method will respond to resume event of our Lifecycle owner (activity/fragment in our case)
// So let's get location here and provide callback
}
#OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun disconnectListener() {
// this method will respond to pause event of our Lifecycle owner (activity/fragment in our case)
// So let's stop receiveing location updates here and remove callback
}
#OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) // Optional if you want to cleanup references
fun cleanUp() {
// this method will respond to destroy event of our Lifecycle owner (activity/fragment in our case)
// Clean up code here
}
}
Now from your activity, you can directly make your LocationUpdates, and receive callback.
class MyActivity : AppCompatActivity() {
private lateinit var mLocationUpdates: LocationUpdates
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Initialize your LifecycleObserver here & assign it to this activity's lifecycle
lifecycle.addObserver(mLocationUpdates)
}
}
You can refer to how to handle Lifecycle & Codelabs example.
Edit:
If you want to have ViewModel for that job, consider this:
class MyViewModel : ViewModel {
private lateinit var mLocationUpdates: LocationUpdates
constructor() : super() {
// initialize LocationUpdates here
}
// Assign our LifecyclerObserver to LifecycleOwner
fun addLocationUpdates(lifecycle: Lifecycle){
lifecycle.addObserver(mLocationUpdates)
}
//Optional, we really don't need this.
fun removeLocationUpdates(lifecycle: Lifecycle){
lifecycle.removeObserver(mLocationUpdates)
}
}
If your LocationUpdates depends upon Context, consider using AndroidViewModel.
We can now observe our location updates # any activity/fragment using LiveData, and assign our LifecycleObserver like below:
class MyActivity : AppCompatActivity() {
private val viewModel: MyViewModel by lazy {
return#lazy ViewModelProviders.of(this#MyActivity).get(MyViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.addLocationUpdates(lifecycle)
}
}
Please note: there's still lot to cover but making this answer as short as possible. So, if you're still confused about something related then please feel free to ask me in comment. I will edit my answer.
with java 8 LifecycleObserver has been deprecated. According to the [docs][1] it is not recommended to use this class as it uses reflection.
Rather the docs recommend using DefaultLifecycleObserver. To do that, extend your ViewModel class with DefaultLifecycleObserver like:
class MyViewModel : ViewModel(), DefaultLifecycleObserver {//implement default lifecycle observer
override fun onCreate(owner: LifecycleOwner) {//override lifecycle events
super.onCreate(owner)
}
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
}
}
and get all the lifecycle event callbacks in your viewmodel by registering your viewmodel as lifecycle event observer in your view class (e.g. Activity class) like:
class MyActivity : AppCompatActivity() {
private val myViewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
lifecycle.addObserver(splashViewModel)//registering observer
...
}
}
its just and update to the answer by #farid_z with kotlin and new sdk.
[1]: https://developer.android.com/reference/androidx/lifecycle/LifecycleObserver
Related
We have a task that is run when the Application is created and we're trying to move the code from our Application object's onCreate to their own Lifecycle aware classes. I'm added my ApplicationLifecycleAwareTaskRunner (a LifecycleObserver) to the lifecycle of the ProcessLifecycleOwner in Application.onCreate() but it's onCreate(owner: LifecycleOwner) is never called. The onStart(..) and onStop() are called as expected.
Is this a known limitation of LifecycleObserver that it cannot observe Application.onCreate() events? or is there something I'm missing here?
Using androidx.lifecycle:lifecycle-runtime-ktx:2.4.1
Adding the observer in the Application object.
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// DI and other init
ProcessLifecycleOwner.get().lifecycle.addObserver(ApplicationLifecycleAwareTaskRunner(..))
}
}
The task runner:
class ApplicationLifecycleAwareTaskRunner(
private val appCoroutineScope: CoroutineScope,
private val myTask: MyTask
) : DefaultLifecycleObserver {
// This is never called :(
override fun onCreate(owner: LifecycleOwner) {
appCoroutineScope.launch {
myTask.invoke()
}
}
...
}
I have viewmodel call TestViewModel and a method call fetchDataFromDataSource() to call fetch data from the server, I used to call load data on OnResume() until I bump into lifecycleScope
I have tried to read more but didn't really get which is better.
class TestViewModel: Viewmodel() {
fun fetchDataFromDataSource(){
....
}
}
class TestActivity : AppCompatActivity() {
private val viewModel: TestViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
...
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Is it best to call here
viewModel.fetchDataFromDataSource()
}
}
}
onResume(){
super.onResume()
// or is it best to call here
viewModel.fetchDataFromDataSource()
}
}
where is the best place to call fetchDataFromDataSource(), is it in onResume() or lifecycleScope and what is the advantage lifecycleScope has over onResume() or onStart()
I know the view has rendered at onResume() so what benefit does lifecycleScope has over android lifecycle (onResume onCreate onStart...)
repeatOnLifecycle is similar to calling methods on the respective lifecycle events every time the Activity hits that state but with a quick access to the lifecycleScope which can launch a coroutine.
Example:
override fun onResume(){
super.onResume()
viewModel.fetchDataFromDataSource()
}
is equivalent to -
class MainActivity : AppCompatActivity {
init {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
viewModel.fetchDataFromDataSource()
}
}
}
}
If you want to load the data from ViewModel every time the user comes to foreground from background, use onStart or repeatOnLifecycle(Lifecycle.State.STARTED).
If you need to load the data everytime the Activity resumes, then use onResume or the lifecycleScope equivalent as shown above but if this is just a one-time op, consider using onCreate.
Google deprecate fragment’s onActivityCreated() on Android and recommend to use LifeCycleObserver:
To get a callback specifically when a Fragment activity's
* {#link Activity#onCreate(Bundle)} is called, register a
* {#link androidx.lifecycle.LifecycleObserver} on the Activity's
* {#link Lifecycle} in {#link #onAttach(Context)}, removing it when it receives the
* {#link Lifecycle.State#CREATED} callback.
So I try to make it in recommended way, but only state I can observe in Logcat is just State: INITIALIZED.
private lateinit var lifecycleObserver: LifecycleObserver
override fun onAttach(context: Context) {
super.onAttach(context)
hostActivity = context as HostActivity
lifecycleObserver = object : LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreate() {
Logger.tag("SOME-TAG")d("State: ${lifecycle.currentState}")
if(lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
Logger.tag("SOME-TAG").d("CREATED")
hostActivity.lifecycle.removeObserver(lifecycleObserver)
}
}
}
hostActivity.lifecycle.addObserver(lifecycleObserver)
}
What is wrong in code above?
UPDATE 1: Looks like I forgot to use hostActivity.lifecycle.currentState and checked fragment's lifecycle instead of Activities lifecycle.
UPDATE 2: Suggested by Google approach not worked for
1 Host activity and 2 fragments when you click back button from one to another, cause onAttach never called, but onActivityCreated called.
As per the changelog here
The onActivityCreated() method is now deprecated. Code touching the
fragment's view should be done in onViewCreated() (which is called
immediately before onActivityCreated()) and other initialization code
should be in onCreate(). To receive a callback specifically when the
activity's onCreate() is complete, a LifeCycleObserver should be
registered on the activity's Lifecycle in onAttach(), and removed once
the onCreate() callback is received.
You can do something like this in your fragment class:
class MyFragment : Fragment(), LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreated() {
// ... Your Logic goes here ...
}
override fun onAttach(context: Context) {
super.onAttach(context)
activity?.lifecycle?.addObserver(this)
}
override fun onDetach() {
activity?.lifecycle?.removeObserver(this)
super.onDetach()
}
}
All I needed was onActivityCreated(...), hence I did implement an observer that:
Automatically removes itself (using .removeObserver(...)).
Then calls passed callback (update()).
I did it in next way:
class MyActivityObserver(
private val update: () -> Unit
) : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
owner.lifecycle.removeObserver(this)
update()
}
}
and use it in fragments onAttach (or another lifecycle method) like:
myActivity.lifecycle.addObserver(MyActivityObserver {
myOnActivityCreated()
})
You can consider the Lifecycle.State as the nodes in a graph and Lifecycle.Event as the edges between these nodes.
So you will never reached the State.Created on your ON_CREATE function.
Solution
class YourFragment : Fragment(), LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onCreated(){
Log.i("tag","reached the State.Created")
}
override fun onAttach(context: Context) {
super.onAttach(context)
lifecycle.addObserver(this)
}
override fun onDetach() {
super.onDetach()
lifecycle.removeObserver(this)
}
}
For more details
https://developer.android.com/topic/libraries/architecture/lifecycle#lc
The best way to solve the issue is to use lifecycleScope which is present in the activity lifecycle. Below is the code snippet
override fun onAttach(context: Context) {
super.onAttach(context)
activity?.lifecycleScope?.launchWhenCreated {
setupActionbar()
}
}
How does it work? launchWhenXxx runs the launch block when it automatically reaches the specified state(in this case it is Created) and if the lifecycle goes to the destroyed state it cancels the launched coroutine automatically. Internally lifecycleScope uses Dispatchers.Main.immediate and hence there is no penalty of thread switching
Pros of this approach are following:
You don't have to manually maintain registering and deregistering of the observer
No need to overwrite two lifecycle methods
You have to latest activity and fragment dependencies to use lifecycleScope attached to the lifecycle
onActivityCreated is deprecated in API level 28.
use onViewCreated for code touching the view created by
onCreateView and onCreate for other initialization. To get a
callback specifically when a Fragment activity's onCreate is called,
register a androidx.lifecycle.LifecycleObserver on the Activity's
Lifecycle in onAttach, removing it when it receives the CREATED
callback.
The annotation #OnLifecycleEvent is deprecated too.
This annotation required the usage of code generation or reflection,
which should be avoided. Use DefaultLifecycleObserver or
LifecycleEventObserver instead.
So, to fix the issue with the deprecated onActivityCreated and OnLifecycleEvent annotation you should do the following:
Implement DefaultLifecycleObserver.
Register your class as observer in onAttach().
Override onCreate(owner: LifecycleOwner) and move your code from onActivityCreated in it.
De-register the observer when the CREATE event is received in onCreate()
See Kotlin and Java examples below:
Kotlin:
class YourFragment : Fragment(), DefaultLifecycleObserver {
override fun onAttach(context: Context) {
super.onAttach(context)
// Register your class as observer
activity?.lifecycle?.addObserver(this)
}
override fun onCreate(owner: LifecycleOwner) {
super<DefaultLifecycleObserver>.onCreate(owner)
// Remove the observer
activity?.lifecycle?.removeObserver(this)
//Move here your code from onActivityCreated(savedInstanceState: Bundle?)
}
}
Java:
public class YourFragment extends Fragment implements DefaultLifecycleObserver {
public void onAttach(#NonNull Context context) {
super.onAttach(context);
// Register your class as observer
if (getActivity() != null) {
getActivity().getLifecycle().addObserver(this);
}
}
#Override
public void onCreate(#NonNull LifecycleOwner owner) {
DefaultLifecycleObserver.super.onCreate(owner);
// Remove the observer
if (getActivity() != null) {
getActivity().getLifecycle().removeObserver(this);
}
//Move here your code from onActivityCreated(savedInstanceState: Bundle?)
}
IMPORTANT: Note that onActivityCreated is called after onCreateView, but DefaultLifecycleObserver.onCreate is called before onCreateView. So, if until now you were using in onActivityCreated something initialised in onCreateView, you'll have to move it somewhere else. E.g. in onViewCreated().
I build my app in MVP architecture and I have a trouble with many functions in my activity and presenter. How Can I decrease a method count?
I have already heard about some solutions:
Split a big presenter into smaller ones but then I would have to create another methods in my activity for presenters connection.
Create a new class and create it instance in my activity which would implement the View interface and will require all of the views needed to manage the presenters. But I am not convinced to this solution. I think it may add another mess to my architecture.
Do you have other ideas or advantages/disadvantages about one described above?
There is more than a way to reduce methods from your Activity/Fragment
One is called inheritance, where you can extend abstract methods into your main Activity/Fragment class and manage the lifecycle from there.
For example, using BaseActivity or BaseFragment you can have more than one method inside of it and just extend that into your main Activity or Fragment
BaseActivity.kt
abstract class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
requestWindowFeature(Window.FEATURE_NO_TITLE)
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
super.onCreate(savedInstanceState)
setContentView(getLayout())
}
#LayoutRes
abstract fun getLayout(): Int
fun Context.toast(message: String?, toastDuration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, message, toastDuration).show()
}
override fun onDestroy() {
super.onDestroy()
//Do here what you want
}
override fun onStop() {
super.onStop()
//Do here what you want
}
override fun onStart(){
super.onStart()
//Do here what you want
}
override fun onPause() {
super.onPause()
//Do here what you want
}
override fun onRestart() {
super.onRestart()
//Do here what you want
}
}
This BaseActivity extends AppCompatActivity(), that means that you can manage the lifecycle of your Activity in this class, and then, just extend it in your main Activity, when you do this, all the functionality inside your BaseActivity will be applied to your MainActivity class, if you want to change or override something, just call the methods from that abstract class.
MainActivity.kt
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//You dont need setContentView since we do all the configuration in the BaseActivity
toast("This is a message with a toast since we implemented thi into the BaseActivity we do not need to do toasts all over again")
}
override fun getLayout(): Int {
return R.layout.activity_login_view
}
//For example, if you want to override the functionallity from a method inside your BaseActivity you can implement it like always
override fun onRestart() {
super.onRestart()
//Replace what BaseActivity onRestart() does
}
Doing this, you can have more than 1 method of your Activity inside your BaseActivity, this will reduce the methods inside your class that inherits from BaseActivity, also, if you need this to work with Fragments, just extend Fragment instead of AppCompatActivity an make a class called BaseFragment
Also, adding an interface for view operations and presenter operations is a great way to organize our apps, you can take a look at an example I'm making for login on Github
Is there any possibility to trigger some default methods in Kotlin interfaces with lifecycle events of, for example, an Activity that implements that interface?
So, I have such interface, that called in Swift - protocol:
interface MyInterface {
fun showToast() {
this as MyActivity
Toast.show(this, "Welcome", Toast.LENGTH_SHORT).show()
}
}
And Activity class:
class MyActivity : AppCompatActivity(), MyInterface {
fun onResume() {
super.onResume()
showToast() //I want this method be called automatically, if possible
}
}
As you can see I should call showToast() method directly. But is there any possibility to call it automatically with, for example, LifeCycleObserver events or somehow else?
You can extend LifecycleObserver interface and use appropriate annotations, for example:
interface LifecycleInterface : LifecycleObserver{
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onLifeResume(){
(this as? Context).let{Toast.makeText(it, "Resumed", Toast.LENGTH_LONG).show()}
}
#OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onLifePause(){
(this as? Context).let{Toast.makeText(it, "Paused", Toast.LENGTH_LONG).show()}
}
}
Then register activity itself (or any custom object for that matter) as listener for lifecycle events:
class MainActivity : AppCompatActivity(), LifecycleInterface {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycle.addObserver(this) // add this to trigger lifecycle methods from interface
setContentView(R.layout.activity_main)
// rest of your onCreate...
}
}
Edit:
After showing bytecode and decompiling back to java, I end up with those two methods injected into activity:
#OnLifecycleEvent(Event.ON_RESUME)
public void onLifeResume() {
DefaultImpls.onLifeResume(this);
}
#OnLifecycleEvent(Event.ON_PAUSE)
public void onLifePause() {
DefaultImpls.onLifePause(this);
}
For anyone still facing issue due to compiler adding a parameter in default function of the interface, this is how you fix it:
Instead of using kapt to process the annotations:
kapt "androidx.lifecycle:lifecycle-compiler:$archLifecycleVersion"
Use annotationProcessor for lifecycler compiler:
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$archLifecycleVersion"
This works with kotlin code.