In my Activity I have a lateinit property called controller that my Fragment uses.
This property is initialized in Activity.onCreate(). My Fragment gets its reference back to my Activity through onAttach(). The Fragment then calls myActivity.controller in Fragment.onCreate().
Normally controller is first initialized in Activity.onCreate(), and after that, the Fragment is added. So this works just fine.
But when my Activity has been killed, it tries to recreate itself and its fragments. This causes Fragment.onCreate() to be called before the initialization took place in Activity.onCreate().
These are the options I see right now:
initialize controller before super.onCreate() (if that's even possible)
move the call to myActivity.controller to a later lifecycle callback, as onViewCreated()
something with ::controller.isInitialized available in Kotlin 1.2
What is my best option here?
By reviewing the Fragment lifecycle, in fact the safest point to do it will be #onActivityCreated(android.os.Bundle).
Even when #onAttach() looks like it is called when the Fragment is attached to the Activity, I'm not sure if this is completely guaranteed, as the old #onAttach(android.app.Activity) is deprecated, and the new #onAttach(android.content.Context) is recommended.
The best way to handle such scenario where an object is used before initialization is by checking with isInitialized() property and then using it.
Related
I have a MainActivity that just hosts the Navigation Component, all the functionality resides in other fragments and I need to use TTS in all of them.
Do I declare a single TextToSpeech instance and pass it around to each fragment (how and where?), or is it better for each of them to have its own instance? I'm already more inclined on choosing the second one but you never know.
Edit: this indecision comes from the need of managing the TTS handle according to the activity or fragment lifecycle.
If I initialize a different TTS field in onCreate() or onCreateView() for each fragment, then I would also need to call TextToSpeech#shutdown() in the fragments' onDestroyView(). It might be the easiest way but I'm concerned about the constant creation and destruction of TTS instances every time the user goes to another fragment.
The alternative would be to initialize the TTS field in my activity's onCreate() and to shut it down in the activity's onDestroy(), but then how would I pass the handle to the fragments while using the Navigation Component? By using Safe Args or a ViewModel?
We utilize LiveData's observe method quite a bit in our fragments. A recent release of the androidx fragment sdk is leading to Android Studio marking instances of liveDataObject.observe(this) as incorrect in favor of liveDataObject.observe(getViewLifecycleOwner()) .
Added a new Lint check that ensures you are using getViewLifecycleOwner() when observing LiveData from onCreateView(), onViewCreated(), or onActivityCreated(). (b/137122478)
https://developer.android.com/jetpack/androidx/releases/fragment
We are worried about implementing this change because we do not understand how the functionality of getViewLifecycleOwner() compares to that of using this, and whether it will cause a conflict with the use of this or this.getActivity() when setting up the ViewModel in the fragment.
Additionally, we use the Android Navigation component and noticed that when the user navigates to different fragments within the same activity, each fragment's onDestroyView() method is called, but not onDestroy()
Here is an example of our code in onViewCreated()
vm.getStemLengths().observe(this, stemLengths -> {
this.stemLengths = new ArrayList<>(Stream.of(stemLengths).map(stemLength ->
new SearchModel(Integer.toString(stemLength.getValue()))).toList());
});
Later, in onDestroyView()
vm.getStemLengths().removeObservers(this);
At the same time, depending on the fragment, the ViewModel that houses the LiveData is set up with one of the following:
vm = new ViewModelProvider(this.getActivity()).get(PrepareVM.class);
To persist the viewmodel across fragments in the activity.
Or:
vm = new ViewModelProvider(this).get(AprobacionVM.class);
If the VM does not need to be persisted outside of the current fragment
So to summarize, will changing this to getViewLifeCycleOwner() when observing LiveData objects in fragments' onCreateView() conflict with the ViewModel patterns / Navigation component? Could there be an instance for example where the livedata change ends up triggering an observer from a previous fragment in the same activity that a user navigates away from?
From the documentation of getViewLifeCycleOwner it seems that making this change may allow us to remove the removeObservers() call in each fragment's onDestroyView(). Is that the correct understanding?
Fragment implements LifecycleOwner which maps its create and destroy events to the fragment's onCreate and onDestroy, respectively.
Fragment.getViewLifecycleOwner() maps its create and destroy events to the fragment's onViewCreated and onDestroyView, respectively. The precise sequence is described here.
If you're working with views in your observers, you'll want the view lifecycle. Otherwise you might get updates when the view hierarchy is invalid, which may lead to crashes.
From the documentation of getViewLifeCycleOwner it seems that making this change may allow us to remove the removeObservers() call in each fragment's onDestroyView().
Correct.
There is already a bug with it. If you do a new Intent for a fragment already use it gives getView is null error. Why? The activity is newly creared.
I have one Activity which handles 5 fragments. Every time the activity replaces each fragment onCreate and onCreateView are being called. In order to avoid this i created a HashMap where i store each fragment. Before the activity replaces a fragment it checks the hashmap if this fragment already exists. If it exists it replaces the old fragment with the instance from the map. In other case it instatiates the fragment and after that it replaces the old own.
Despite i avoid the instation of the fragment when i find it on hashmap, the onCreate and onCreateView are being called. How can i avoid this? Is there any other way to achieve my goal?
First of all there is no use for a HashMap to save the references of your Fragments. You can set a tag to a Fragment at the point you add/replace it. Have a look at the FragmentTransaction.add(int, Fragment, String) and FragmenTransaction.replace(int, Fragment, String) methods. If you provide a unique String for the tag you can retrieve the Fragment with the FragmentManager.findFragmentByTag(String) method. A container for Fragment references is redundant.
To the point:
If you use the replace/add method to show a Fragment the onCreate() and onCreateView() is called. To avoid the onCreate() call you can just attach and detach your Fragments. This way only onCreateView() will be invoked. But it's not possible to prevent the onCreateView() call.
Maybe update your question with some details what you want to achieve, because it sounds you are completely on the wrong track.
Your goal is not very cleared.
When you deal with fragment, keep in mind that your control over their life cycle is limited, you only extend (system controlled) object. You can read on the life cycle of the fragment here: Creating a Fragment.
Assuming your goal is to switch between 5 active fragments, I can think of two options:
option 1: design your fragment so they can be recreated quickly, maintain the data in some other place, and provide it to the fragment, which only do the work of display the data.
option 2: The android support library has two fragment adapters, FragmentPagerAdapter, and FragmentStatePagerAdapter. The first is an adapter which keep the fragments in memory.
How can i avoid this? Is there any other way to achieve my goal?
If you really want to avoid your activity instance to be recreated again and again just use android:launchMode="singleTop".
Example:
<activity
android:name=".YourActivity"
android:label="SomeLabel"
android:launchMode="singleTop">
</activity>
From developer docs,
If an instance of the activity already exists at the top of the target
task, the system routes the intent to that instance through a call to
its onNewIntent() method, rather than creating a new instance of the
activity.
Source: http://developer.android.com/guide/topics/manifest/activity-element.html
I'm currently having issues with fragment lifecycle management.
Should the activity the fragment is hosted in be recreated I have set SetRetainInstanceState(true) to keep the fragment instance alive.
However, this had lead to some strange behaviour regarding my views. Sometimes I get memory leak warnings concerning a few fragment views and nullpointer exceptions to the activity context.
Wanting to make sure the fragment instance is retained properly: what are best practices regarding the retaining of a fragment (what to keep, what to destroy)?
SetRetainInstanceState(true) makes sure Android retains the fragment while the activity is being recreated. Therefore the activity the fragment was first attached to is not longer there after activity recreation and the fragment is attached to a new activity instance.
To make sure this goes well keep the following things in mind:
Do not keep a reference to the attached activity in your fragment unless absolutely necessary. Use the getActivity() method instead which will always return the currently attached fragment (or null if nothing is attached).
If you absolutely have to have a "permanent reference" to the currently attached activity (in which you might want to rethink your design) make sure to update this reference in the onAttach and onDetach methods.
Make sure you retain no object that was initialized using the activity as a context (usually views, adapters and such). To do this, override the Fragments onDestroyView() method that gets called just before activity recreation. Here you can dispose of the views and adapters the fragment still has a active reference to (usually just setting their reference to null should be enough). You can then recreate the fragment's views and adapters using the new context in the onCreateView call.
I'm trying to change the activity title from a fragment (in this case, it's an android.support.v4.app.Fragment). To this end, I save the activity in an attribute on the fragment when onAttach() is called on the fragment. According to the docs, onAttach() should be called before onCreateView(), which I'm using to request some data used to fill up the view. When I kick off the thread for the network retrieval, I want to indicate that in the title, so I'm trying to call this.activity.setTitle() from the Fragment. However, that keeps throwing a NullPointerException. What am I missing here?
You can access the Activity in a Fragment using getActivity(). It can be called safely as soon as onActivityCreated() was called on the Fragment. Before that, it might not be there or might not have been fully initialized yet.
If your thread starts before that, just note the fact somewhere in your Fragment and only change the title after onActivityCreated was called.
Nowadays you can call requiredActivity() too that return FragmentActivityobject and if fragment doesn't come from an Activity, method throws a IllegalStateException