Problem: Surviving app process being killed (saving presenter object instance in correct manner so it can be re-used later on)
Here is a simple example fragment which is using MVP architecture.
class xFragment : BaseFragment() {
private lateinit var xPresenter: xPresenter
override lateinit var xAdapter: BaseAdapter
override fun onResume() {
super.onResume()
xPresenter.view = this
xAdapter = xAdapter(
xPresenter,
this,
this
)
recyclerView.adapter = contentAdapter
}
override fun onPause() {
super.onPause()
}
override fun onDestroy() {
if (xPresenter.view != null) xPresenter.view = null
super.onDestroy()
}
override fun onDeleteButtonClicked() {
x.onDeleteButtonClicked()
}
companion object {
#JvmStatic fun createInstance(presenter: xPresenter): xFragment {
val fragment = xFragment()
fragment.xPresenter = presenter
return fragment
}
}
I read that the best solution to save anything is doing it in onPause() and restore it in onResume(). As you can see, I pass my Presenter when I create my fragment in "createInstance" method. How should a save my xPresenter so I can retain it later on in onResume and procceed without errors?
If your process was killed, you can only restore things that were saved in bundle during call of Activity.onSaveInstanceState(Bundle). So you can't save presenter instane until it implements Serializable/Parcelable interfaces (only primitive types or serializable/parcelable objects can be stored in bundle). All saved data you could restore in Activity.onCreate(Bundle) or Activity.onRestoreInstanceState(Bundle).
Related
When an action is performed I am finishing the activity that I am currently on and landing on the fragment that was previously opened.
My question is, is there a way to tell that fragment that is being resumed from finishing a specific activity?
You can use LiveData approach.
Create a constant in companion object in fragment class as follow:
companion object {
val _isActivityFinished = MutableLiveData<Boolean>()
val isActivityFinished: LiveData<Boolean> get() = _isActivityFinished
}
In onDestroy() of your activity
override fun onDestroy() {
_isActivityFinished.postValue(true)
super.onDestroy()
}
In fragment, observe the state of this variable
isActivityFinished.observe(viewLifecycleOwner) {
if (it) {
// do your work.. it has been returned from activity
}
}
I'm subscribed to an observable in my Fragment, the observable listens for some user input from three different sources.
The main issue is that once I navigate to another Fragment and return to the one with the subscription, the data is duplicated as the observable is handled twice.
What is the correct way to handle a situation like this?
I've migrated my application to a Single-Activity and before it, the subscription was made in the activity without any problem.
Here is my Fragment code:
#AndroidEntryPoint
class ProductsFragment : Fragment() {
#Inject
lateinit var sharedPreferences: SharedPreferences
private var _binding: FragmentProductsBinding? = null
private val binding get() = _binding!!
private val viewModel: ProductsViewModel by viewModels()
private val scanner: CodeReaderViewModel by activityViewModels()
private fun observeBarcode() {
scanner.barcode.observe(viewLifecycleOwner) { barcode ->
if (barcode.isNotEmpty()) {
if (binding.searchView.isIconified) {
addProduct(barcode) // here if the fragment is resumed from a backstack the data is duplicated.
}
if (!binding.searchView.isIconified) {
binding.searchView.setQuery(barcode, true)
}
}
}
}
private fun addProduct(barcode: String) {
if (barcode.isEmpty()) {
return
}
viewModel.insert(barcode)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.start(args.documentId)
if (args.documentType == "Etichette") {
binding.cvLabels.visibility = View.VISIBLE
}
initUI()
observe()
}
private fun observe() {
observeBarcode()
observeProducts()
observeLoading()
observeLast()
}
}
Unfortunately, LiveData is a terribly bad idea (the way it was designed), Google insisted till they kinda phased it out (but not really since it's still there) that "it's just a value holder"...
Anyway... not to rant too much, the solution you have to use can be:
Use The "SingleLiveEvent" (method is officially "deprecated now" but... you can read more about it here).
Follow the "official guidelines" and use a Flow instead, as described in the official guideline for handling UI Events.
Update: Using StateFlow
The way to collect the flow is, for e.g. in a Fragment:
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) { // or RESUMED
viewModel.yourFlow.collectLatest { ... } // or collect { ... }
}
}
For that in your ViewModel you'd expose something like:
Warning: Pseudo-Code
// Imagine your state is represented in this sealed class
sealed class State {
object Idle: State
object Loading: State
data class Success(val name: String): State
data class Failure(val reason: String): State
}
// You need an initial state
private val _yourFlow = MutableStateFlow(State.Idle)
val yourFlow: StateFlow<State> = _yourFlow
Then you can emit using
_yourFlow.emit(State.Loading)
Every time you call
scanner.barcode.observe(viewLifecycleOwner){
}
You are creating a new anonymous observer. So every new call to observe will add another observer that will get onChanged callbacks. You could move this observer out to be a property. With this solution observe won't register new observers.
Try
class property
val observer = Observer<String> { onChanged() }
inside your method
scanner.barcode.observe(viewLifecycleOwner, observer)
Alternatively you could keep your observe code as is but move it to a Fragment's callback that only gets called once fex. onCreate(). onCreate gets called only once per fragment instance whereas onViewCreated gets called every time the fragment's view is created.
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.
I have this database that contains information about celebrities, lots of information, ie movies, roles, articles...
Without the viewmodel, all of the stuff in the edit text gets lost. So i dont want to lose all of that
info on rotation.
So i send an intent with all the relevant info of a selected celebrity(sql room) to the addeditactivity, so now how do i initialize the viewmodel? If I do getStringExtra in onCreate, wouldn't that just rewrite the viewmodel again when the activity gets recreated?
how do i get around this, also is there a better alternative? im a beginner, thanks in advance!
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ......... shared preferences code .................
// get intent code ................
mViewModel = ViewModelProviders.of(this).get(testViewModel.class);
mViewModel.celebName = intent.getStringExtra(CELEB_NAME);
// similar..........
}
No, ViewModels have separate lifecyles from Activities. When your Activity gets rotated/recreated it will still use your previously-instantiated ViewModel (and any data saved in it) unless it has already been cleared.
See https://developer.android.com/topic/libraries/architecture/viewmodel
If the activity is re-created, it receives the same MyViewModel
instance that was created by the first activity. When the owner
activity is finished, the framework calls the ViewModel objects's
onCleared() method so that it can clean up resources.
You can initialize your viewModel in onCreate() using ViewModelFactory to pass your data from getStringExtra to your viewModel. Use viewModelFactory pattern to pass data to your viewModel. Here "YourINFOParameter" is your data from getStringExtra. So after initialization in your viewModel you have set parameter you can observe using liveData. I am suing Kotlin in answer.
as example:
override fun onCreateView(
........
val viewModelFactory =
TestViewModelFactory(
yourINFOparameter,
application)
val testViewModel = ViewModelProvider(
this, viewModelFactory
).get(TestViewModel::class.java)
.................
Then declare class TestViewModelFactory:
class TestViewModelFactory(
private val yourINFOparameter: String, private val application: Application) : ViewModelProvider.Factory {
#Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(TestViewModel::class.java)) {
return TestViewModel(yourINFOparameter,
application) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
And your viewModelclass:
class TestViewModel(yourINFOparameter: String, application: Application) : AndroidViewModel(application) {
............................}
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