Your wording hints some design flaws, namely:
You are refering to your data as object variable and to make it accessible at all times you chose to use a ViewModel. To me it sounds that you overthought your options.
Suggestion
Your object lifecycle appears to be managed manually by yourself. Therefore you should just use a static variable. This translates to Kotlin as a property within an (companion) object. I suggest you declare a custom Application class within your manifest and in its onCreate
-method, you allocate your object and put it into the companion object of this class. Of course you can allocate it at any given time later on as well.
This will result in the following:
- Access is always be possible via
YourApplication.mData
within your code.
- Objects which relying on implementations outside the JVM can be managed properly.
For example: If you already bound to a port you won't be able to do this on a successive call - When the viewModel restores its state, for example. Maybe the underlying implementation did not report an error back to Java but allocating did not succeed. To manifest this assumption you would need to provide an description of your object variable. But As an famous example in the world of Android for this behaviour, try creating a soundPool via the SystemServices. You will experience lints about the correct usage of this object.
- Deallocating can be done in the
onTerminate()
method of your
Application.class // edit_4: Doc of super.onTerminate() says the system will just kill your app. Therefore one needs to deallocate within an your activity. See code snippets below.
Clarification
The ViewModel of the JetPack Components is mainly responsible for saving and restoring the state of the view and binding to its model.
Meaning it handles the lifecycle across activities, fragments and possibly views. This is why you have to use an activity as the lifecycle owner in case you want to share an viewModel across multiple fragments. But I still suppose your object is more complex than just a POJO and my above suggestion results in your expected behaviour.
Also note that when multithreading, you shall not rely on the correct order of the lifecycle methods. There are only limited lifecycle-callbacks which are guaranteed to be called in a specific order by the android system, but the frequently used ones are unfortunately not included here. In this case, you should start processing at a more approrpiate time.
Even though the data should be similiar to the previous state, the exact reference depends on the hashCode implementation, but this is an JVM specific.
// edit:
ParentFragment is also bad naming, since you created a class which others shall inherit instead of refer to. If you want to access a specific variable within all your fragments, this needs to be implemented as an object (Singleton), since the Navigation component will prevent you from accessing the fragmentManager directly.
In plain android, one fragment can always refer to its parentFragment, iff this parentFragment has used its own childFragmentManager to commit the fragmentTransaction. Meaning also that fragments added by your Activity-fragmentManager have never an parentFragment.
// edit_2+3:
ViewModelProvider(activity!!, ViewModelFactory())[clazz]
is the correct call for creating and accessing a sharedViewModel:
The lifecycle owner needs to be the activity, otherwise after each fragmentTransaction done there will be a callback to the onCleared() method and the viewModel will release all references to avoid memory leaks.
// edit_4:
That your object was not correctly initialized was just an assumption which only would oocure if you tried to initialize it again. For example if you use an get()-method on an val where not appropriate.
Nonetheless, handling your object this way ensures that its lifecycle is outside your fragments. Here is an code example to clarify my wording:
// edit_5: To assert that the object reference is not damaged, include null checking (only if construction of CustomClass is non trivial)
Declare your CustomApplication
class CustomApplication : Application() {
companion object SharedInstances {
/**
* Reference to an object accessed in various places in your application.
*
* This property is initialized at a later point in time. In your case, once
* the user completed a required workflow in some fragment.
*
* @Transient shall indicate that the state could also be not Serializable/Parcelable
* This _could_ require manually releasing the object.
* Also prohibits passing via safeArgs
*/
@Transient var complex: CustomClass? = null
}
}
Intialization and Usage within your classes:
class InitializeComplexStateFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (complex != null) return@onViewCreated // prohibit successive initialization.
if (savedInstanceState != null) { /* The fragment was recreated but the object appears to be lost. */ }
// do your heavy lifting and initialize your data at any point.
CustomApplication.SharedInstances.complex = object : CustomClass() {
val data = "forExampleAnSessionToken"
/* other objects could need manual release / deallocation, like closing a fileDescriptor */
val cObject = File("someFileDescriptorToBindTo")
}
}
}
class SomeOtherFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
CustomApplication.SharedInstances.complex?.let {
// do processing
}
?: propagateErrorStateInFragment()
}
private fun propagateErrorStateInFragment() { throw NotImplementedError("stub") }
}
Deallocation if required
class SomeActivity: Activity() {
override fun onStop() {
super.onStop()
/* with multiple activities the effort increases */
CustomApplication.complex?.close()
}
}