How to tell a fragment that an Activity was finished? - android

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
}
}

Related

How to prevent data duplication caused by LiveData observation in Fragment?

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.

Different between Android onResume(), onStart() and lifecycleScope

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.

android-implement ViewModel between 2 fragments without using getActivity()

I have 2 fragments in my app. when the user clicks on a button in the first fragment, the second fragment gets added so that the user can insert some data. and then it gets closed and gives the inserted data back to the first fragment. I've used ViewModels for this communication between fragments.
collectionsEditedViewModel = new ViewModelProvider(getActivity()).get(CollectionsEditedViewModel.class);
collectionsEditedViewModel.isEdited().observe(getViewLifecycleOwner(), new Observer<Bundle>() {
#Override
public void onChanged(Bundle bundle) {
}
});
the communication is working properly. but the point is that how can I define the scope of this communication within the fragments. currently I'm using getActivity() as ViewmodelStoreOwner which causes the set data to be redelivered to the first fragment whenever it is reopened. how can I solve this issue?
I believe for communication between Fragments, via the Activity is the way to go, so you're in the correct path.
One thing you could do is use a SingleLiveData class, which essentially is like LiveData but after its value is set, it's nullified, so only the first observer gets it:
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"
}
}
Then you can simply call:
singleLiveData.call() for a "set and destroy" and thus, forget your value after the first use! Class retrieved (and been using in my projects for years) from: https://stackoverflow.com/a/46862551/

why coroutine method in ViewModel is continuing to process after leaving Fragment?

Here is my method I am calling updateProgress() in Fragment onCreate()
and after navigating forward to another Activity or Fragment this updateProgress is still continue to work. How can I stop this ?
I was expecting if I am navigating to another Activity or Fragment
ViewModel onCleared() should be called and it will stop this update
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
fun updateProgress() {
uiScope.launch {
delay(UPDATE_PROGRESS_INTERVAL)
updateCurrentProgramProgress()
contentRowsMutableData.postValue(contentRows)
updateProgress()
}
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
The onCleared is called when the fragment is destroyed. If you move to another fragment, the previous one will remain in the backstack (if you used addToBackstack) and thus is paused, but not destroyed.
If you want to stop processing data when the fragment is paused, you can have your ViewModel implement the LifecycleObserver interface, then you would add
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() {
// add code here to stop work
}
You can then also resume processing with this
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun start() {
// code here
}
However, note that working while the fragment is paused may be desirable, so when the user returns to the fragment the work is complete and data can be shown immediately. It all depends on your specific circumstances.

How to save presenter instance when app process is being killed

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

Categories

Resources