Question regarding delegation of Viewmodel property(Android)(Kotlin) - android

Whenever I am trying to delegate my viewmodel using the delegated property in my Fragments
val viewModel:NewsViewModel by activityViewModels<> { }
This is the error I would receive.
Property delegate must have a 'getValue(BreakingNews, KProperty*>)' method. None of the following functions are suitable.
Lazy<NewsViewModel>.getValue(Any?, KProperty<*>)   
where T = NewsViewModel for inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property:
KProperty<*>): T defined in kotlin
However, instantiating the viewmodel instance in the MainActivity this way, seems fine
val viewmodelFactory = ViewModelProviderFactory(dataRepo)
viewModel = ViewModelProvider(this,viewmodelFactory).get(NewsViewModel::class.java)
I assumed at first it may be due to the viewmodelFactory which is as follows.
class ViewModelProviderFactory(val Repo:NewsRepository): ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>,extras: CreationExtras): T {
return NewsViewModel(Repo) as T
}
}
The code below are the properties in my ViewModel, This is odd as I have cross-referred from multiple sources including the official `docs.
var breakingNews: MutableLiveData<Resource<ArticleList>> = MutableLiveData()
var breakingNewsPage = 1
var breakingNewsResponse: ArticleList? = null
var searchNews: MutableLiveData<Resource<ArticleList>> = MutableLiveData()
var searchNewsPage = 1
var searchNewsResponse: ArticleList? = null
var savedNumerics = 0
var _status = MutableLiveData<String>()
....
}

Usually, you want just val viewModel:NewsViewModel by activityViewModels().
In your case, with val viewModel:NewsViewModel by activityViewModels<> { }, you skipped two things.
First, you are attempting to provide a value for the extrasProducer parameter to the activityViewModels() function. However, that lambda expression needs to evaluate to a CreationExtras object, and yours evaluates to Unit.
Second, you failed to provide a type. AFAIK, <> is not going to be valid syntax here. Either leave the <> off (and the compiler should infer the type from the property type) or fully qualify it as <NewsViewModel>. I think this is the cause of your specific syntax error, but even if you fix this, you should then run into a problem with your empty lambda expression.

Related

How to create separate ViewModels per list item when using Compose UI?

I'm working on a trading app. I need to list the user stocks and their value (profit or loss) among with the total value of the portfolio.
For the holdings list, in an MVP architecture I would create a presenter for each list item but for this app I decided to use MVVM (Compose, ViewModels and Hilt ). My first idea was to create a different ViewModel for each list item. I'm using hiltViewModel() in the composable method signature to create instances of my ViewModel, however this gives me always the same instance and this is not what I want. When using MVVM architecture, is what I'm trying to do the correct way or I should use a single ViewModel? Are you aware about any project I could have a look at? The image below is a super simplification of my actual screen, each cell is complex and that's why I wanted to use a different ViewModel for each cell. Any suggestion is very welcome.
Hilt doesn't support keyed view models. There's a feature request for keyed view models in Compose, but we had to wait until Hilt supports it.
Here's a hacky solution on how to bypass it for now.
You can create a plain view model, which can be used with keys, and pass injections to this view model through Hilt view model:
class SomeInjection #Inject constructor() {
val someValue = 0
}
#HiltViewModel
class InjectionsProvider #Inject constructor(
val someInjection: SomeInjection
): ViewModel() {
}
class SomeViewModel(private val injectionsProvider: InjectionsProvider) : ViewModel() {
val injectedValue get() = injectionsProvider.someInjection.someValue
var storedValue by mutableStateOf("")
private set
fun updateStoredValue(value: String) {
storedValue = value
}
}
#Composable
fun keyedViewModel(key: String) : SomeViewModel {
val injectionsProvider = hiltViewModel<InjectionsProvider>()
return viewModel(
key = key,
factory = object: ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
#Suppress("UNCHECKED_CAST")
return SomeViewModel(injectionsProvider) as T
}
}
)
}
#Composable
fun TestScreen(
) {
LazyColumn {
items(100) { i ->
val viewModel = keyedViewModel("$i")
Text(viewModel.injectedValue.toString())
TextField(value = viewModel.storedValue, onValueChange = viewModel::updateStoredValue)
}
}
}
Unfortunately, HiltViewModelFactory is not a KeyedFactory. So as of now it does not support same viewModel with multiple instances.
Tracking: https://github.com/google/dagger/issues/2328
You have to use Dagger version 2.43 (or newer), it includes the feature/fix to support keys in Hilt ViewModels
https://github.com/google/dagger/releases/tag/dagger-2.43
From the release description:
Fixes #2328 and #3232 where getting multiple instances of #HiltViewModel with different keys would cause a crash.

Dynamic lazy initialization for val in kotlin

I understand there are two ways for lazy initialization in kotlin. first by lateinit which is dynamic but it is only for var. second, by lazy delegate which is for val but it is static, which means it can't be initialized at runtime.
I was wondering is there a way to have lazy dynamic initialization for immutable properties(val)????
property delegation also works like lazy and even if we define a custom delegate, its always static initialization. (to my knowledge)
is there a workaround for this? could it be implemented somehow?
so what I wish for, is something like lateinit val, shown in below code:
class MyClass: SomeCallback {
private lateinit val myData: String
override fun onStatusChanged(status: Status, data: String) {
if(status == Status.DataConfirmed ) {
myData = data
}
}
}
The best I can come up with is a read-write property delegate that throws if you access it before setting it, or if you set it multiple times. Kotlin doesn't let you lateinit a val. This is likely because it would be nonsensical to call a setter for a property that doesn't have one. I doubt they want to introduce the can of worms it would be to directly set the value of a backing field from anywhere besides the initializer, because it would be ambiguous.
A delegate like this should be adequate. If it's not adequate to help you immediately fix the bug of calling the setter multiple times, I would say that's a code smell that the class is too complicated and needs to be broken up into smaller units.
class Once<T>: ReadWriteProperty<Any, T> {
private object UNINITIALIZED
private var _value: Any? = UNINITIALIZED
override fun getValue(thisRef: Any, property: KProperty<*>): T {
if (_value !== UNINITIALIZED) {
#Suppress("UNCHECKED_CAST")
return _value as T
}
throw UninitializedPropertyAccessException("Property [$property] was accessed before it was initialized.")
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
if (_value === UNINITIALIZED) {
_value = value
} else {
error("Cannot set property [$property] more than once.")
}
}
}

LiveData unit testing error when using postValue in init block

I'm trying to write a unit test for a view model using live data.
LoginViewModel.kt
class LoginViewModel #Inject constructor(
val context: Context
): ViewModel() {
val username = MutableLiveData<String>()
val password = MutableLiveData<String>()
val isLoginButtonEnabled = MediatorLiveData<Boolean>().apply {
fun combineLatest(): Boolean {
return !(username.value.isNullOrEmpty() || password.value.isNullOrEmpty())
}
addSource(username) { this.value = combineLatest() }
addSource(password) { this.value = combineLatest() }
}
init {
username.postValue("test")
password.postValue("test")
}
}
LoginViewModelTest.kt
#RunWith(MockitoJUnitRunner::class)
class LoginViewModelTest {
#Rule
#JvmField
val instantTaskExecutorRole = InstantTaskExecutorRule()
private val context = mock(Context::class.java)
private val loginViewModel = LoginViewModel(context)
#Test
fun loginButtonDisabledOnEmptyUsername() {
val observer = mock<Observer<Boolean>>()
loginViewModel.isLoginButtonEnabled.observeForever(observer)
loginViewModel.username.postValue("")
verify(observer).onChanged(false)
}
}
My unit test throws the following exception at the line username.postValue("test"):
java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details.
The InstantTaskExecutorRule should provide an execution context when using live data, however it doesn't work when initializing live data in the init-block. When omitting the init-block it works as desired, but i need the possibility to initialize live data variables.
Is there any way to make the live data initialization work when unit testing view models?
I managed to unit test my ViewModel that was using LiveData using mentioned rula - InstantTaskExecutorRule. But in my case the rule val declaration was a bit different:
#Suppress("unused")
#get:Rule
val instantTaskExecutorRule: InstantTaskExecutorRule = InstantTaskExecutorRule()
Edit:
#Before
#Throws(Exception::class)
fun prepare() {
MockitoAnnotations.initMocks(this)
}
Edit2:
For some weird reason I cannot reproduce this :)
Also, I think that the problem could be because of the way you're initializing your ViewModel -
private val loginViewModel = LoginViewModel(context)
I assume that it initializes too early, thus it's init block gets called too early too. Maybe it's reasonable to create it in the #Before method ? Like:
private lateinit var viewModel: LoginViewModel
#Before
#Throws(Exception::class)
fun prepare() {
loginViewModel = LoginViewModel(context)
}
I was seeing a similar issue when setting a LiveData value during the ViewModel's init. Demigod's solution pointed me in the right direction, but I wanted to explain a bit about what was going on and why in the lifecycle of the testing process.
When you have a ViewModel that sets the LiveData during init, it will be run as soon as the view model is initialized. When you initialize the view model in your unit test using val viewModel = MyViewModel(), that view model is instantiated at the same time as the test class is initialized. The problem there is any rules you may have are initialized at the same time, but are not actually run until after the class is completely initialized, so your ViewModel.init() is happening before the rules actually take effect. This means your live data isn't working on an instant executor, any Rx observables aren't being run on replaced schedulers, etc. So ultimately there are two ways of solving for this:
Define the view model as a lateinit var and initialize the view model as a in the #Before method of your test, which runs after rules are applied, or
Define the view model as a val viewModel by lazy { MyViewModel() }, which won't be run until you actually start calling it in your tests.
I prefer option 2 because it also allows me to set up any test-case-specific preconditions before my view model is ever initialized, and I don't have to do repetitive initialization code (which could be quite verbose) inside every test that requires it.
I had a similar issue and the answer provided by Demigod was not solving it. I finally found out where the devil was hiding so I share it here : my init block was set before the liveData initialization, which works fine when running the app, but not when running tests !
class MyViewModel : ViewModel() {
// init { // <-- Do not put the init block before the liveData
// _myLiveData.postValue("First")
// }
private val _myLiveData: MutableLiveData<String> = MutableLiveData()
val myLiveData: LiveData<String>
get() = _myLiveData
init {
_myLiveData.postValue("First")
}
}

Convenient Kotlin LoggerFactory simplification

What is the most convenient way to use SLF4J or other logging approaches with kotlin?
Usually the developer is busy with boilerplate code like
private val logger: Logger = LoggerFactory.getLogger(this::class.java)
in each and every class to get a proper logger?
What are the most convenient ways to unify/simplify this with Kotlin?
You can define an extension property on every type:
val <T : Any> T.logger: Logger
get() = LoggerFactory.getLogger(this::class.java)
use it as follows:
class X {
init {
logger.debug("init")
}
}
Here's a simple example which returns a lazily-initialized logger from a bound callable reference or a standard property. I prefer calling from a callable reference because the :: denotes reflection (related to logging).
The class which provides the Lazy<Logger>:
class LoggingProvider<T : Any>(val clazz: KClass<T>) {
operator fun provideDelegate(inst: Any?, property: KProperty<*>) =
lazy { LoggerFactory.getLogger(clazz.java) }
}
Inline functions to call them:
inline fun <reified T : Any> KCallable<T>.logger() =
LoggingProvider(T::class)
inline fun <reified T : Any> T.logger() =
LoggingProvider(T::class)
Here's an example of using them. The require assertion in the initializer shows that the loggers share a reference:
class Foo {
val self: Foo = this
val logger by this.logger()
val callableLogger by this::self.logger()
init {
require(logger === callableLogger)
}
}
I define this function in my projects to make defining a logger easier for me. It takes advantage of Kotlin's reified types.
// Defined in Utilities.kt
inline fun <reified T:Any> logFor() =
LoggerFactory.getLogger(T::class.java)
Usage:
class MyClass {
private val log = logFor<MyClass>()
...
}
Or if you are creating a lot of them:
class MyClass {
companion object {
private val log = logFor<MyClass>()
}
...
}
if you don't like the boilerplate, you can always wrap the log.info with your own logger helper:
mylog.info(this, "data that needs to be logged")
Then in the background, have some sort of hashmap that keeps track of classes of the this param that can instantiate a logger for that class.
Other options might be using AspectJ Weaving to weave a logger into each class, but this is overkill in my opinion.
I have defined a utility method for this
fun getLogger(cl: KClass<*>): Logger {
return LoggerFactory.getLogger(cl.java)!!
}
and now in each class I can use the logger like this
companion object {
private val logger = getLogger(MyClass::class)
}

Android data binding with Kotlin, BaseObservable, and a custom delegate

I'm attempting to write a custom delegate which would clean up the syntax for databinding in a Kotlin class. It would eliminate the need to define a custom getter and setter for every property I might want to observe.
The standard implementation in Kotlin appears to be as follows:
class Foo : BaseObservable() {
var bar: String
#Bindable get() = bar
set(value) {
bar = value
notifyPropertyChanged(BR.bar)
}
}
Clearly, with a lot of properties this class can become pretty verbose. What I would like instead is to abstract that away into a delegate like so:
class BaseObservableDelegate(val id: Int, private val observable: BaseObservable) {
#Bindable
operator fun getValue(thisRef: Any, property: KProperty<*>): Any {
return thisRef
}
operator fun setValue(thisRef: Any, property: KProperty<*>, value: Any) {
observable.notifyPropertyChanged(id)
}
}
Then, the class which extends BaseObservable could go back to having one-line variable declarations:
class Foo : BaseObservable() {
var bar by BaseObservableDelegate(BR.bar, this)
}
The problem is that without the #Bindable annotation in the Foo class, no propertyId is generated in BR for bar. I'm unaware of any other annotation or method for generating that property id.
Any guidance would be appreciated.
You can annotate the default getter or setter without providing a body.
var bar: String by Delegates.observable("") { prop, old, new ->
notifyPropertyChanged(BR.bar)
}
#Bindable get
There is a shortcut annotation use-site target which does the same thing.
#get:Bindable var bar: String by Delegates.observable("") { prop, old, new ->
notifyPropertyChanged(BR.bar)
}
Additionaly to the accepted answer - sometimes you need variables passed in constructor. It is easy to do too.
class Foo(_bar: String) : BaseObservable() {
#get:Bindable var bar by Delegates.observable(_bar) { _, _, _ ->
notifyPropertyChanged(BR.bar)
}
}
Sometimes we have to save object using parcel, I had some problems using delegete, so code looks like this:
#Parcelize
class Foo(private var _bar: String) : BaseObservable(), Parcelable {
#IgnoredOnParcel
#get:Bindable var bar
get() = _bar
set(value) {
_bar = value
notifyPropertyChanged(BR.bar)
}
}
I considered using the androidx.databinding.ObservableField wrapper for my fields. However, it was quite annoying having to read the values as field.get() and write them field.set(value) from the Kotlin code. Also, this approach does require special converters for serialization if you are using it with Retrofit or Room Database.
Finally, I came up with the below approach which allows me to define the variable in a single line as oppose to the accepted answer and keep the field to their default type without any wrapper. Thanks to the Kotlins property delegation. Now, I don't have to write converters for the serialization and have all the benefit from databinding.
class ObservableField<T : BaseObservable, V>(initialValue: V, private val fieldId: Int = -1) : ReadWriteProperty<T, V> {
private var value: V = initialValue
override fun getValue(thisRef: T, property: KProperty<*>): V {
return value
}
override fun setValue(thisRef: T, property: KProperty<*>, value: V) {
this.value = value
if (fieldId == -1) {
thisRef.notifyChange()
} else {
thisRef.notifyPropertyChanged(fieldId)
}
}
}
class Credential: BaseObservable() {
var username: String by ObservableField("")
#get:Bindable var password: String by ObservableField("", BR.password)
}

Categories

Resources