I'm new to android, I wanted to know if it is okay to access properties initialized in activity / call activity functions from fragment like this or is it bad practice and I should avoid it.
(requireContext() as BaseActivity).viewModel
(requireContext() as BaseActivity).countryList
(requireContext() as BaseActivity).getSomething()
Your instincts can be right. Breaking changes can be caused by name conflicts, variable shadowing, wrong imports, wrong assignment to values. But these days, the demand for features is increasing, in such that you need the public accessor. Just have this rules in your conscience:
Interfaces are powerful at class to class communication
Inherit what is important, override what is implemented already, pass to param to lessen global var damage
If a variable can be stored in another global form, consider it with regard to size(ram matters), speed of access(ux matters), security(keys matter) and volatility(nulls matter).
Now looking at your code, I can see you have a fragment system that is based on values/functions stored in the main activity, that provides the context for the fragment. If you apply the first point: Your fragment will implement a BaseFragment that already some context cast i.e. lateinit var mainActivity: MainActivity then you can mainActivity.viewModel anywhere in your fragment without casting. And this is cleaner
Applying the second point: in the BaseFragment (that will be inherited by AnotherFragment)
abstract var viewModel: ViewModel
abstract fun initList()
open var countryList = mutableListOf()
open fun onScale(detector: ScaleGestureDetector) { //pinch: increase visible country list like some nice zoom effect .. etc }
if most or all of your fragments need similar functions or variables, make abstract to something you can forget will crash the app, make open for those 'features but I dont need to rewrite so I'll call super.function' functions. Make a var open if some super function overrides it, and just put var if you seriously dont know when you want it and when to change it.
On the third point, Android in the early stages, we learnt the hard way that context doesn't last forever even if your app is running. Rotation and lifecycle functions will swap it rough and fast. So consider other storage ways. I still dont trust requireContext/Activity/view for context, so cast with caution.
A big NO. It is a bad practice to use hardcode references to activity from fragments.
I see that you are using viewmodel, which indicates that you are using MVVM, you should use Sharing data between Activities and Fragments in MVVM made simple with SharedViewModel concept for communicating to viewmodel of the activity.
For communicating to the Activity which hosts your Fragment you should use an interface pattern of communication. from fragment to the activity
Let's say one of your activity won't extend the base activity as it has to extend one activity from a library lets say YouTubeBaseActivity, and it will host a particular fragment now the cast to BaseActivity will never succeed in your activity.
Related
Google states to use SharedViewModel in case of communication between Fragments by scoping it to the activity.
In a Single Activity Application, this means that the activity will be littered with ViewModels which might not be needed anymore and they will stay there for the whole lifecycle. Considering some example like a extended sign up flow or something considering a few screens.
One way recommended was using a parent Fragment as scope, but that is only possible if there is a parent fragment. It can be just different Fragments.
I came up with the following solution, I want to know how viable or how bad is it and is there any better way around?
Considering I have two Fragments called ExampleOneFragment and ExampleTwoFragment for sake of simplicity and I want them to have a shared scope without actually scoping it to the activity. Let's say I want to update text view in ExampleOneFragment from ExampleTwoFragment so I create a SharedViewModel like this for both
For ExampleOneFragment it will be:
private val mSharedViewModel by lazy {
ViewModelProvider(this).get(SharedViewModel::class.java)
}
And For ExampleTwoFragment I came up with this:
private val mSharedViewModel by lazy {
ViewModelProvider(supportFragmentManager().findFragmentByTag(ExampleOneFragment.TAG) ?: this).get(SharedViewModel::class.java)
}
This seems to be working, but I don't know what kind of issues can this cause.
Some Other Solutions are which I found:
According to #mikhehc here We could actually create our very own ViewModelStore instead. This will allow us granular control to what scope the ViewModel have to exist. But I don't understand how to make it work for Fragments?
Secondly, is the hacky way of scoping it to the activity still, but clearing it out via dummy viewmodel by using same key which I found here
Could anyone guide me what is the right approach? I cannot shift to NavGraphs since it is an already up and running project and scoping to activity just feels wrong. Thanks.
This seems to be working, but I don't know what kind of issues can this cause.
This code will only work if:
An ExampleOneFragment is created first, always
It is added to the same FragmentManager that ExampleTwoFragment uses, via a tag of ExampleOneFragment.TAG, always
If either of those assumptions fail, you will wind up with separate viewmodel instances, because supportFragmentManager().findFragmentByTag(ExampleOneFragment.TAG) ?: this will resolve to this.
But I don't understand how to make it work for Fragments?
You would use it the way it is shown in that answer, or by anything else that accepts a ViewModelStoreOwner. In this line:
val viewModel = ViewModelProvider(myApp, viewModelFactory).get(CustomViewModel::class.java)
You would get myApp from your Fragment as:
val myApp = requireContext().application as MyApp
Personally, I think the solution that you pointed to is very risky. The ViewModelStore lives for the entire lifetime of the process and is never cleared. You will wind up sharing your viewmodel across everything, and everything done by that viewmodel is leaked. The concept of creating a custom ViewModelStoreOwner is fine, but you should be doing something to tie the scope of that owner to the relevant lifetime for the viewmodels that it stores. The answer tries to dance around that in its last paragraph; too many developers will ignore this and run into problems.
One way recommended was using a parent Fragment as scope, but that is only possible if there is a parent fragment. It can be just different Fragments.
Your app is not written in a way to make automatic ViewModelStoreOwner management available "out of the box", then.
In the end, you are looking for a ViewModelStoreOwner to be shared between these two fragments. In your first solution, you are trying to hack that by using the ViewModelStoreOwner from one of those fragments, which only works if you can reliably choose which fragment that is. In the solution you pointed to in that other answer, you are trying to hack that by intentionally leaking a ViewModelStoreOwner.
There may be other approaches that you could consider, depending on your circumstances. For example, there may be some option with your dependency inversion framework (Dagger/Hilt, Koin, etc.) to rig up a ViewModelStoreOwner that is tied to a specific pair of fragment instances.
Without extending ViewModel() just with simple class I am able to implement LiveData and DataBinding example but I show in google developer doc to extend ViewModel() to create object of LiveData.So why we need to extend it?
https://developer.android.com/topic/libraries/architecture/livedata
If you create a variable say var a = 10 in Fragment or Activity, then you change it somewhere (ex: a button click), now it becomes 50, then you rotate the screen (known as Configuration Change), you will notice that a becomes 10 again. Sounds bothering sometimes huh? ViewModel is designed for solving this problem.
Any variable you declare inside ViewModel will not be affected by Configuration Change.
Of course you must extend (inherit) your class from ViewModel or AndroidViewModel to gain this ability. But If you don't need this feature, you don't need to extend them.
Following shows the cases which will lead to configuration change:
Rotate screen
Change system language
Plug in physical keyboard
Connected to a mouse
KEY POINT: After configuration change, Activity will be recreated, thus data might be lost.
currently, my Kotlin based application consists of a single activity, 3 fragments and a navigation (with navigation drawer) between them.
How do I add a variable, which will be initialized in the start of the application, will be visible in all fragments, and can be updated in one of them?
a simple int or string for that matter so it should be with little overhead as possible, yet i'd like to follow correct coding conventions. Please elaborate on the correct function to perform the initial variable value, how to bind each fragment textview to it, and the correct way to set the new value.
Thanks!
Create ViewModel and fragments use viewmodel like this
val viewModel: YourViewModel by activityViewModels()
in that case your viewmodel's scope is the same as activity scope.
For more information please refer to this link
I have an app with the following architecture:
Navigator is a custom class that holds the NavController
Cooridnator holds the Navigator
Cooridnator tells the Navigator to "start" the framgent and passes the ViewModel to it
Navigator asks NavController to navigateTo a NavDirections and provides the required arguments (using Safe-Args)
Now the issue here is that if I want to send the ViewModel as argument, it needs to be Parcelable and all of its underlying classes as well (which would make most of my code Parcelable, and that's not really needed).
So is there a way to do this without making everything Parcelable or using Dagger ? (Don't like Dagger as it adds too much complexity to the code...)
I would be okay with having a lateinit field in the Fragment and setting it manually but can't seem to access the Fragment from NavDirections
Any idea on how I could do this ?
First of all: what you are passing in safe args is "data" while your viewmodel is logic. Which means your data can change over the time (one of examples would be to become outdated) but as long as viewmodel is unchanged, it's logic would stay. Thus passing viewmodel itself does not make sense to me - best you can is to pass its snapshot of state, but I doubt that's what you want.
So yes, you should be using DI and there are alternatives to dagger complexity. You can experiment with koin (because I see kotlin in your tags list), some basic outline of what it can is here https://shorturl.at/bflFL (medium). You can also experiment with Hilt as what appears to be simplified alternative to Dagger, for android world.
I am building an app that needs to detect certain objects in photos. I first load and keep all the photos I'd like to inspect into my room Database and connect it to my view with a ViewHolder.
When the user clicks a button, I want the detection process to begin, and I want to use a jobIntentService for that (As there might be thousands of photos there).
My problem is - how do I access the view holder from within the Service? I need it both to actually get a hold of the files and also so I can update each file's record once detection has been made.
I've tried to ask for the activity as one of the attributes, but I am getting this error
Unable to instantiate service tech.levanter.anyvision.services.DetectJobIntentService: java.lang.InstantiationException: java.lang.Class<tech.levanter.anyvision.services.DetectJobIntentService> has no zero argument constructor
Would appreciate any input, thanks
ViewModels holds particular significance for Activities and Fragments (e.g. they retain data during config changes). So a Service doesn't really need it. Hence you can resolve the issue in one of two ways.
Approach 1:
If your MyViewModel is just a wrapper for accessing LiveData from your Repository class, then you can just use your Repository class inside your Service.
Approach 2:
If your ViewModel is doing more than just wrapping calls to the Repository and you want your Service class to have access to the same logic defined in your ViewModel, then use an intermediate ViewModelContent class. Instead of putting everything in your MyViewModel class, put them in a "ViewModelContent" class. Then use your MyViewModel class as an accessor wrapper around ViewModelContent. Then your Service can instantiate ViewModelContent as you would any other class.
class MyViewModel(application: Application) : AndroidViewModel(application) {
init{
viewModelContent = ViewModelContent(...)
}
}
Approach 1 will usually be cleaner than Approach 2.