How should I interpret the Android Developers ViewModel lifecycle diagram? - android-lifecycle

The following diagram appears in the Android Jetpack ViewModel Overview:
Why does the upper onDestroy graphic in the middle column have a pointy end and not terminate the ViewModel Scope while the other onDestroy graphic has a square end and terminate the ViewModel Scope?

I was able to get in touch with the creator of the diagram, Jose Alcérreca, who told me "the second onDestroy doesn't have an arrow is because it's the end of the activity's lifecycle (triggered by finish() not a recreation)."
I was also pointed to the source code for ComponentActivity, which shows the observer for ON_DESTROY:
getLifecycle().addObserver(new LifecycleEventObserver() {
#Override
public void onStateChanged(#NonNull LifecycleOwner source,
#NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if (!isChangingConfigurations()) { // ***
getViewModelStore().clear(); // ***
}
}
}
});
As the starred lines show, the clear() method is called for the ViewModelStore only if the call to onDestroy() is not due to a configuration change.

Related

ViewModel Live Data observers calling on rotation

In my view model, I have two properties:
private val databaseDao = QuestionDatabase.getDatabase(context).questionDao()
val allQuestions: LiveData<List<Question>> = databaseDao.getAllQuestions()
I have observers set on "allQuestions" in my fragment and I'm noticing the observer is being called when I rotate the device. Even though the View Model is only being created once (can tell via a log statement in init()), the observer methods are still being called.
Why is this? I would think the point is to have persistency in the View Model. Ideally, I want the database questions to be only loaded once, regardless of rotation.
This happens because LiveData is lifecycle aware.
And When you rotate the screen you UI Controller [Activity/Fragment] goes through various lifecycle states and lifecycle callbacks.
And since LiveData is lifecycle aware, it updates the detail accordingly.
I have tried to explain this with following points:
When the UI Controller is offscreen, Live Data performs no updates.
When the UI Controller is back on screen, it gets current data.
(Because of this property you are getting above behavior)
When UI controller is destroyed, it performs cleanup on its own.
When new UI Controller starts observing live data, it gets current data.
add this check inside observer
if(lifecycle.currentState == Lifecycle.State.RESUMED){
//code
}
I have the same issue, after reading the jetpack guideline doc, I solve it. Just like what #SVK mentioned, after the rotation of the screen, activity/fragment were re-created.
Base on the solution https://stackoverflow.com/a/64062616,
class SingleLiveEvent<T> : MutableLiveData<T>() {
val TAG: String = "SingleLiveEvent"
private val mPending = AtomicBoolean(false)
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in 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> { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
override fun observeForever(observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observeForever { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
}
#MainThread
override fun setValue(#Nullable t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#MainThread
fun call() {
value = null
}

How do we remove the inline code listener?

Suppose we have added listener to a changeNotifier object in a view like following:
someChangeNotifierObject.addListener((){ if (this.mounted) setState(){}});
Do we need to remove the listener explicitly when the view is getting disposed?
What would be the impact if not removing it?
How to remove it since it is an inline code?
If you add a listener to a super object (an object located outside of current state) and you use setState inside of the listener - in that case you should remove it explicitly on current state dispose.
Otherwise, setState will throw an exception when the object notifies its listeners, as the state inside of which you added a listener would have been disposed to that moment.
Do not use inline functions in addListener. If you need mounted - it can be accessed anywhere throughout the State, except for static methods. Hence, simply create a new function inside of the current State class.
e.g.
#override
void initState() {
super.initState();
someChangeNotifierObject.addListener(myListenerFunc);
}
#override
void dispose() {
someChangeNotifierObject.removeListener(myListenerFunc);
super.dispose();
}
void myListenerFunc() {
print("Heya the object has changed!");
setState(() {
// re-render current stateful widget.
});
}
However, it is simply a good practice to remove listeners on dispose - no matter whether you use State's methods inside of a listener or not.

ICommand not always firing when tab selected

I have a simple ActionBar with 3 tabs attached. When a tab is clicked, the fragment is inflated and the view shows. The tab being click event is fired using an event. Initially, the first fragment is inflated, but the others respond and inflate if clicked.
If I change the event being fired to an ICommand, only the last fragment is inflated and then if I click on the first tab, that and the last are inflated. Never the second.
My code is this
ICommand TabClicked
{
get
{
return new RelayCommand(() =>
{
tab.TabSelected += (object sender, ActionBar.TabEventArgs e) => TabOnTabSelected(sender, e);
});
}
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
fragments.Add(new TODFragment());
fragments.Add(new ConditionsFragment());
fragments.Add(new ResultsFragment());
AddTabToActionBar("Time", Resource.Drawable.crucifix_colour);
AddTabToActionBar("Conditions", Resource.Drawable.weather_colour);
AddTabToActionBar("Results", Resource.Drawable.tod_colour);
}
void AddTabToActionBar(string text, int iconResourceId)
{
tab = ActionBar.NewTab().SetTag(text).SetText(text).SetIcon(iconResourceId);
/* uncomment and comment out one of the two below to see the difference in operation */
tab.TabSelected += TabOnTabSelected;
//tab.SetCommand<ActionBar.TabEventArgs>("TabSelected", TabClicked);
ActionBar.AddTab(tab);
}
void TabOnTabSelected(object sender, ActionBar.TabEventArgs tabEventArgs)
{
var tabNo = sender as ActionBar.Tab;
var frag = fragments[tabNo.Position];
tabEventArgs.FragmentTransaction.Replace(Resource.Id.frameLayout1, frag);
}
Am I missing something fundamental here in the difference between ICommands and Events or is it something else?
I'm using Xam.Android and MVVMLight
I found the answer. When I create the partial class I define the UI objects like this (or something like this at least)
EditText myEditText;
EditText MyEditText = myEditText ?? (view.FindViewById<EditText>(Resources.Id.myEdit);
This is fine, but it does mean that once defined, it doesn't get redefined.
Not a problem if the UI is not really going to change, but every time an action tab is pressed, the fragment is refreshed. Only problem is the Id isn't changing as myEditText is not null.
The answer is add a method in the UI definition code that nulls the objects then in the main code, when the UI disappears, call the nulling method. Everything works then

How to remove an event handler in Acumatica

I have added code that I would believe should remove an event handler of an acumatica base class.
public override void Initialize()
{
// Remove the event handler on the APTran object for APTran_SubID_FieldDefaulting so we can override it in this module
Base.FieldDefaulting.RemoveHandler<APTran.subID>(A PTran_SubID_FieldDefaulting);
}
Yet, it still runs through the base code. If I remove this code and keep my new event handler, it runs through the event handler I have and then also appears to run through the Base event handler.
Any suggestions on how to remove the base event handler so it will just use the new event handler?
Solution
Turns out (thanks go to Ivan)
that I do not need to remove the event handler, I just need to declare the event handler in the extension with a third parameter that points back to the original event handler, then I can choose to not call that delegate if that is what I need in my project.
example:
protected void APTran_SubID_FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e, PXFieldDefaulting del)
{
APTran row = (APTran)e.Row;
if (row == null) { return; }
// do my code here
// skip calling the original event
//del.Invoke(sender, e); -- invokes the Base FieldDefaulting event handler
}

After clicking a button, how do I wait for SwingWorker to finish before proceeding?

I have the following code:
public class FileLoader extends SwingWorker(Void, Void) {
#Override
private Void doInBackground() {
loadFiles();
}
}
public class LogInPage {
private FileLoader fileLoader = new FileLoader();
public LogInPage() {
fileLoader.execute();
}
loginButtonActionPerformed(ActionEvent evt) {
//wait for files to finish loading
//while displaying a waiting cursor
showMainForm();
}
}
My question would be:
After clicking the button, I would want all the files to be loaded first (while displaying an hourglass cursor and progress bar) before showing the main form.
I have done this before with Thread's join() but was not able to do the same with SwingWorker.
I have read about overriding done() and implementing listeners but I can't apply it here.
Any help?
Thanks.
From what you're saying and contrary to what you think, I think you can actually use SwingWorker's done() method. Before execute(), disable the button, start a busy animation, whatever, then in the done() method, do whatever it is you need to do to continue the program. That's what it's for :-)
You should also look at the SwingWorker.publish() and process() to send and receive the progress bar events.
See also: How do I wait for a SwingWorker's doInBackground() method?

Resources