Understanding top level get() in Kotlin - android

While I was checking this repository about dagger in android with kotlin, I stumbled upon the application class:
class ConnectingTheDotsApp : Application() {
val appComponent: AppComponent by lazy {
DaggerAppComponent
.factory()
.create(this)
}
override fun onCreate() {
super.onCreate()
appComponent.inject(this)
}
}
val Activity.appComponent get() = (application as ConnectingTheDotsApp).appComponent
val Fragment.appComponent get() = (requireActivity().application as ConnectingTheDotsApp).appComponent
The class I understand. But the two last lines of code, I cannot figure them out. I know that get() is a backing property but why is it outside a class and what about the Activity. and Fragment. what did they mean? Any Idea?
Thanks

Don't mind guys. As IR42 stated, it's just a simple implementation of the extension properties in koltin.

Related

Cannot access ViewModel method from fragment

Probably it has a simple solution that I cant see. I have a fragment with a ViewModel, The Viewmodel has a method inside of it that I want to call from my fragment and supply the arguments for. but when I try to call the method it shows an error "Unsolved Reference"
class DetailFragmentViewModel : ViewModel() {
private val repo = Crepository.get()
private val itemIdlivedata = MutableLiveData<UUID>()
var crimeLiveDate: LiveData<Crime?> = Transformations.switchMap(itemIdlivedata){ it ->
repo.getitem(it) }
fun LoadItem(itemuuid:UUID){
itemIdlivedata.value = itemuuid
}
}
Fragment Class:
private val crimeDetailVM : ViewModel by lazy {
ViewModelProvider(this).get(DetailFragmentViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
crimeDetailVM.LoadItem <- Unsolved Reference
}
Thanks for the help!
EDIT:IT HAS A SIMPLE SOLUTION, I DID NOT CAST THE VIEW MODEL TO THE VIEW MODEL CLASS,THANKS FOR THE HELP EVERYONE
You are doing downcasting DetailFragmentViewModel to ViewModel. That is why you are not accessing to DetailFragmentViewModel methods.
Use
private val crimeDetailVM : DetailFragmentViewModel by lazy {
ViewModelProvider(this).get(DetailFragmentViewModel::class.java)
}
Instead of
private val crimeDetailVM : ViewModel by lazy {
ViewModelProvider(this).get(DetailFragmentViewModel::class.java)
}
Also this way is not idiomatic i suggest you to use kotlin extension
val viewModel by viewModels<DetailFragmentViewModel>()
But before do that you need to add the dependency which is Fragment KTX to your app gradle file.
https://developer.android.com/kotlin/ktx
You need activity context
try:
ViewModelProvider(requireActivity()).get(DetailFragmentViewModel::class.java)
you can use also extend view model by ActivityViewModel
eg.-> class DetailFragmentViewModel(application:Application) : AndroidViewModel(applivation){}

how to instantiate ViewModel In AndroidX?

I want to initialize ViewModel in Activity using androidx library
I have tried what documentation says but it is not working. the ".of" is not resolved.
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import com.example.myapplication.databinding.ActivityMainBinding`
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(
this, R.layout.activity_main)
binding.setLifecycleOwner(this)
var model = ViewModelProvider.of(this).get(SheduleViewModel::class.java)
}
}
of is not resolved, maybe there are another way to do it in androidx
Updated answer:
Things changed a little bit, as the previously needed dependency - ViewModelProviders - got deprecated (see the old answer for details). You can now use the ViewModelProvider constructor directly.
So, in this case, the answer would be:
private val viewModel = ViewModelProvider(this).get(SheduleViewModel::class.java)
Note that, however, if you include the androidx.activity:activity-ktx:$Version dependency (a few of the commonly used AndroidX dependencies already include it for you), you can make use of property delegation:
private val viewModel: SheduleViewModel by viewModels()
Which internally will use ViewModelProvider and scope your ViewModel to your Activity. It's just a more concise way of writing the same thing. You can do the same for a Fragment by including the androidx.fragment:fragment-ktx:$Version dependency instead (again, commonly already included by other AndroidX dependencies).
Both the ViewModelProvider constructor and by viewModels() also accept a factory as a parameter (useful for injecting your ViewModel):
private val viewModel =
ViewModelProvider(this, viewModelFactory).get(SheduleViewModel::class.java)
and
private val viewModel: SheduleViewModel by viewModels { viewModelFactory }
Use the one that best suits you.
Old answer:
Add the androidx.lifecycle:lifecycle-extensions:$lifecycleExtensionsVersion dependency in order to import ViewModelProviders.
Updating ViewModel to Lifecycle Version 2.2.0 and Above
The ViewModels (VMs) may theoretically be initialized as class level instance variables using the Kotlin extension library import androidx.fragment.app.viewModels method by viewmodels(). By initializing the VM as a class level instance var it can be accessed within the class.
Question: Is there a downside to initializing the VMs as class level instance variables instead of inside onCreate?
When creating the VMs with the extension function inside onCreate the VMs are only scoped within onCreate and extra code is required to reassign the class level instance variables.
See documentation
ViewModel Overview
Lifecycle
Initialize VM as Class Instance Val
class Fragment : Fragment() {
private val viewModel: SomeViewModel by viewModels()
private fun observeViewState() {
viewModel.feedViewState.observe(viewLifecycleOwner) { viewState ->
//viewState used here.
}
}
}
Initialize VM in onCreate and Reassign Class Instance Var
class Fragment : Fragment() {
private lateinit var viewModel: SomeViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel: ContentViewModel by viewModels()
this.viewModel = viewModel
}
private fun observeViewState() {
viewModel.feedViewState.observe(viewLifecycleOwner) { viewState ->
//viewState used here.
}
}
}
Passing Arguments/Parameters
// Override ViewModelProvider.NewInstanceFactory to create the ViewModel (VM).
class SomeViewModelFactory(private val someString: String): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = SomeViewModel(someString) as T
}
class SomeViewModel(private val someString: String) : ViewModel() {
init {
//TODO: Use 'someString' to init process when VM is created. i.e. Get data request.
}
}
class Fragment: Fragment() {
// Create VM in activity/fragment with VM factory.
val someViewModel: SomeViewModel by viewModels { SomeViewModelFactory("someString") }
}
Enabling SavedState with Arguments/Parameters
class SomeViewModelFactory(
private val owner: SavedStateRegistryOwner,
private val someString: String) : AbstractSavedStateViewModelFactory(owner, null) {
override fun <T : ViewModel?> create(key: String, modelClass: Class<T>, state: SavedStateHandle) =
SomeViewModel(state, someString) as T
}
class SomeViewModel(private val state: SavedStateHandle, private val someString: String) : ViewModel() {
val feedPosition = state.get<Int>(FEED_POSITION_KEY).let { position ->
if (position == null) 0 else position
}
init {
//TODO: Use 'someString' to init process when VM is created. i.e. Get data request.
}
fun saveFeedPosition(position: Int) {
state.set(FEED_POSITION_KEY, position)
}
}
class Fragment: Fragment() {
// Create VM in activity/fragment with VM factory.
val someViewModel: SomeViewModel by viewModels { SomeViewModelFactory(this, "someString") }
private var feedPosition: Int = 0
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
someViewModel.saveFeedPosition((contentRecyclerView.layoutManager as LinearLayoutManager)
.findFirstVisibleItemPosition())
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
feedPosition = someViewModel.feedPosition
}
}
For me, the only thing that worked:
implementation 'androidx.fragment:fragment:1.2.4'
PS. This is for someone who is using java and got stuck for a while like I did and this SO answer comes up in google all the time.
Apparently, the API has change as of this date (6 May 2020), I had to do this to get it working.
// 1. Create a ViewModel Class Let's call it AppStateViewModel
// 2. Put below code Inside Activity onCreate like this:
ViewModelProvider.Factory factory = new ViewModelProvider.NewInstanceFactory();
appStateManager = new ViewModelProvider(this, factory).get(AppStateViewModel.class);
ViewModelProviders: This class is deprecated. Use the constructors for ViewModelProvider directly.
Examples in Kotlin
This is how you can use ViewModelProvider directly:
If your view-model is extending AndroidViewModel with just one argument, the app - then you can use the default AndroidViewModelFactory and you don't have to write a new Factory. Example:
// Activity / fragment class
private lateinit var viewModel: MyOwnAndroidViewModel
// onCreate
viewModel = ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory(application)
).get(MyOwnAndroidViewModel::class.java)
If your view-model is only extending the ViewModel without extra arguments then use the NewInstanceFactory().
// Activity / fragment class
private lateinit var viewModel: MyOwnViewModel
// onCreate
viewModel = ViewModelProvider(
this,
ViewModelProvider.NewInstanceFactory()
).get(MyOwnViewModel::class.java)
Adam's answer above covers other variations as well.
Disclaimer: Still learning basic Android development - if there's any problem with the code, let me know in comments.
(How to) Use ViewModel from Android Architecture Component :
Add the Google Maven repository (Optional, just verify that)
Android Studio projects aren't configured to access this repository by default.
To add it to your project, open the build.gradle file for your project (not the ones for your app or module) and add the google() repository as shown below:
allprojects {
repositories {
google()
jcenter()
}
}
Declaring dependencies
Open your app-level build.gradle file,
Go to dependencies{} block
Put implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" for AndroidX version, $lifecycle_version here is latest version defined.
For Pre-AndroidX use implementation "android.arch.lifecycle:viewmodel:1.1.1" (1.1.1 is the last version from this artifact i guess.)
In your activity, use like this syntax
Import this class :
import androidx.lifecycle.ViewModelProviders; for AndroidX
import android.arch.lifecycle.ViewModelProviders; when using Pre-AndroidX
And obtain your ViewModel like following
ViewModelProviders.of(this).get(ProfileObservableViewModel::class.java) // Kotlin syntax
---- or ----
ViewModelProviders.of(this).get(ProfileObservableViewModel.class); // Java syntax
In your app gradle file make sure you have added below dependencies:
For Activity use:
implementation "androidx.activity:activity-ktx:1.4.1"
For Fragment use:
implementation 'androidx.fragment:fragment:1.4.1'
Paste the code below in build.gradle(:app)
implementation 'androidx.fragment:fragment-ktx:1.4.1'
paste the following or similar(relevant to your settings) in app.gradle under dependencies
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
I add the last version of this dependency from
https://developer.android.com/kotlin/ktx/extensions-list
implementation "androidx.activity:activity-ktx:1.4.0"

Koin how to inject outside of Android activity / appcompatactivity

Koin is a new, lightweight library for DI and can be used in Android as well as in standalone kotlin apps.
Usually you inject dependencies like this:
class SplashScreenActivity : Activity() {
val sampleClass : SampleClass by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
with the inject() method.
But what about injecting stuff in places where Activity context is not available i.e. outside of an Activity?
There is the KoinComponent which comes to the rescue. In any class you can simply:
class SampleClass : KoinComponent {
val a : A? by inject()
val b : B? by inject()
}
Extending KoinComponent gives you access to inject() method.
Remember that usually it's enough to inject stuff the usual way:
class SampleClass(val a : A?, val b: B?)
Koin provides a solution for this using the KoinComponent interface. For example, if you need to get some dependencies in your repository then you can simply implement the KoinComponent interface. It gives you access to various Koin features such as get() and inject(). Use KoinComponent only when you can't rewrite the constructor to accept dependencies as constructor parameters.
class MyRepository: Repository(), KoinComponent {
private val myService by inject<MyService>()
}
Constructor injection is better than this approach.
For example, the same thing can be achieved by:
class MyRepository(private val service: MyService): Repository() {
...
}
And you can add the definition for instantiating this class in a koin module:
val serviceModule = module {
...
factory { MyService() }
}
val repositoryModule = module {
...
factory { MyRepository(get<MyService>()) }
}
If you don't want to implement any interfaces then just take a look at how KoinComponent.inject() is implemented and do something similar yourself:
val foo by lazy { KoinPlatformTools.defaultContext().get().get<FooClass>() }

How to initialize/inject generic ViewModel in BaseActivity by Koin injection on Android/Kotlin App

I'm building the architecture of a new Android application using Kotlin and Android Architecture Components (ViewModel, LiveData) and I'm also using Koin as my dependency injection provider.
The problem is that I'm not been able to initialize the ViewModel in a generic way inside my BaseActivity via koin injection. The current code looks like this:
abstract class BaseActivity<ViewModelType : ViewModel> : AppCompatActivity() {
// This does not compile because of the generic type
private val viewModel by lazy {
// Koin implementation to inject ViewModel
getViewModel<ViewModelType>()
}
#CallSuper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Fabric.with(this, Crashlytics())
}
/**
* Method needed for Calligraphy library configuration
*/
#CallSuper
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase))
}
}
I'd like to know if is there a way to do this in Kotlin because I'm pretty sure I would be able to do in Java easily.
Thanks.
The solution was provided by the koin team in version 0.9.0-alpha-11 and the final code looks like this:
open class BaseActivity<out ViewModelType : BaseViewModel>(clazz: KClass<ViewModelType>) :
AppCompatActivity() {
val viewModel: ViewModelType by viewModel(clazz)
fun snackbar(message: String?) {
message?.let { longSnackbar(find(android.R.id.content), it) }
}
fun toast(message: String?) {
message?.let { longToast(message) }
}
}
Here is example of not passing Class and Generic to base implementation
In your base fragment/activity:
abstract class BaseFragment<T : BaseViewModel> : Fragment() {
...
#Suppress("UNCHECKED_CAST")
private val clazz: KClass<T> = ((this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<T>).kotlin
protected val viewModel: T by viewModel(clazz = clazz)
...
}
It looks ugly, but it works.
you can use a delegate version declaration for your ViewModel and avoid using directly a lazy expression. Try with this:
abstract class BaseActivity<T : ViewModel> : AppCompatActivity() {
val model by viewModel<T>()
}
This will give you a lazy of
getViewModel<T>()
Throw an eye on the quick ref: https://insert-koin.io/docs/1.0/getting-started/android-viewmodel/
Hope it will help.

Dagger 2 + Kotlin can't inject Presenter into View

I am trying to create simple MVP Archtecture app using Dagger 2. I am tying to achieave same result as in this tutorial, but with Kotlin. Here is my code so far.
Presenter:
class MainPresenter #Inject constructor(var view: IMainView): IMainPresenter{
override fun beginMessuring() {
view.toastMessage("Measuring started")
}
override fun stopMessuring() {
view.toastMessage("Measuring stopped")
}
}
View:
class MainActivity : AppCompatActivity(), IMainView {
#Inject lateinit var presenter : MainPresenter
val component: IMainComponent by lazy {
DaggerIMainComponent
.builder()
.mainPresenterModule(MainPresenterModule(this))
.build()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
component.inject(this)
presenter.beginMessuring()
}
override fun toastMessage(message: String) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}
}
Dagger Module:
#Module
class MainPresenterModule(private val view: IMainView) {
#Provides
fun provideView() = view
}
Dagger Component:
#Component(modules = arrayOf(MainPresenterModule::class))
interface IMainComponent {
fun inject(mainView : IMainActivity)
}
The problem is that I am getting build error which starts with this:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.maciej.spiritlvl/com.example.maciej.spiritlvl.View.MainActivity}: kotlin.UninitializedPropertyAccessException: lateinit property presenter has not been initialized
PS, my gradle dagger config:
kapt 'com.google.dagger:dagger-compiler:2.9'
mplementation 'com.google.dagger:dagger:2.9'
EDIT:
Changed injected presenter type from IMainView to MainView.
Whenever trying to inject any interface, like in your case IMainPresenter, you need to tell dagger which concrete implementation to use. Dagger has no means of knowing which implementation of that interface you want to 'have' (you might have numerous implementations of that interface).
You did the right thing for the IMainView by adding a #Provides-annotated method to your module. You can do the same for your presenter, but that imho would render the whole point of dagger useless, because you'd have to create the presenter yourself when creating the module.
So I would, instead of injecting the IMainPresenter interface into your activity, inject the concrete implementation MainPresenter. Then you also shouldn't need a #Provides method in your module (for the presenter).

Categories

Resources