I'm trying to convert the following Java Code to Kotlin. It compiles and works fine.
public abstract class MvpViewHolder<P extends BasePresenter> extends RecyclerView.ViewHolder {
protected P presenter;
public MvpViewHolder(View itemView) {
super(itemView);
}
public void bindPresenter(P presenter) {
this.presenter = presenter;
presenter.bindView(this);
}
public void unbindPresenter() {
presenter = null;
}
}
In the code I currently have, I get an error on the presenter.bindView(this) that states Required: Nothing, Found: MvpViewHolder.
abstract class MvpViewHolder<P>(itemView: View) : RecyclerView.ViewHolder(itemView) where P : BasePresenter<*,*> {
protected var presenter: P? = null
fun bindPresenter(presenter: P): Unit {
this.presenter = presenter
//I get the error here
presenter.bindView(this)
}
fun unbindPresenter(): Unit {
presenter = null
}
}
bindView is defined like so
public abstract class BasePresenter<M,V> {
fun bindView(view: V) {
this.view = WeakReference(view)
}
}
The only thing I can attribute it to right now is not defining the class generics correctly. As far as I can tell, this is still a correct instance of the View generic that is expected as the parameter, I also definitely don't see how it could be Nothing. How can I fix the bug?
EDIT: Java code for BasePresenter
public abstract class BasePresenter<M, V> {
protected M model;
private WeakReference<V> view;
public void bindView(#NonNull V view) {
this.view = new WeakReference<>(view);
if (setupDone()) {
updateView();
}
}
protected V view() {
if (view == null) {
return null;
} else {
return view.get();
}
}
}
Your bindView method requires a View as an argument.
The error you're seeing appears when you define a variable (view defined on BasePresenter) that can hold a null result or, in your case, a View object.
On the code below you're binding this as the argument and MapViewHolder isn't a subclass of View.
abstract class MvpViewHolder<P>(itemView: View) : RecyclerView.ViewHolder(itemView) where P : BasePresenter<*,*> {
protected var presenter: P? = null
fun bindPresenter(presenter: P): Unit {
this.presenter = presenter
//I get the error here
presenter.bindView(this) // -> this references MvpViewHolder which isn't a subclass of View
}
fun unbindPresenter(): Unit {
presenter = null
}
}
I think that what you want is to attach the itemView to the presenter because it is in fact a View object.
EDIT
The problem has to do with defining BasePresenter<*,*> which means in this case BasePresenter<Nothing, Nothing> (Nothing is an object in kotlin) - read more about star projections in kotlin on this link.
I would advice defining explicitly the types base presenter is expecting or defining explicitly as BasePresenter<Any?, Any?>.
You cannot directly use this to point to current class.
You need to use
this#class_name .
For example if "Example" is the class name, you can use
this#Example
It means this in Java
For more info visit https://kotlinlang.org/docs/reference/this-expressions.html
Related
For define Util(common functions) we can use 3 approach :
First :
We can define a File and add function to it like this :
fun func1(): String {
return "x"
}
fun func2(): String {
return "y"
}
Then we can use this in every where like this :
fileName.func1()
Second :
We can define a class and add these functions to class like this :
class Util{
fun func1(): String {
return "x"
}
fun func2(): String {
return "y"
}
}
we can inject it and use this like :
private val mUtil: Util by inject()
mutil.func1()
Third:
We can define an object like this :
object Util{
fun func1(): String {
return "x"
}
fun func2(): String {
return "y"
}
Then we can use this like :
Util.func1()
suppose that we have reusable fun that it use in different class , activity or fragment now is better to use first , second or third ?
According to my understanding :
In the first example, we simply define a function to be used within the scope of component (could be a fragment or an activity ..)
In the second example, which I think more used in Java than Kotlin, you simply create a class in which you define some static variable or function to be accessed in your components later
Example 3, I have no idea as I never used that
first one is static function
second one is normal function
third is singleton function
you can decomplie to see the java code and see their differs.
Kotlin file
fun fun1() {}
class TestClass {
fun testFun() {}
}
object ObjectClass {
fun objectFun() {}
}
Java file
public final class TTTKt {
public static final void fun1() {
}
}
public final class TestClass {
public final void testFun() {
}
}
public final class ObjectClass {
public static final ObjectClass INSTANCE;
public final void objectFun() {
}
private ObjectClass() {
}
static {
ObjectClass var0 = new ObjectClass();
INSTANCE = var0;
}
}
Im new to kotlin, and mvvm, but i was able to make it work in java, but when i made a new example mvvm-retrofit-corutines in kotlin, the view model gets called all the time on the OnCreate function is called, (which shouldn't happen according to docs and works fine in java).
MainActivity:
lateinit var viewModel : MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Here we can see the logs in every orientation changed in the emulator.
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
viewModel.getMutableLiveDataModel().observe(this, Observer {
Log.d("zzzz","lamda executes onChanged method -> "+ it.otherValues). //element from model
})
}
MyViewModel:
class MyViewModel : ViewModel() {
private lateinit var objectTypeModel: MutableLiveData<MyTestModel>
fun getMutableLiveDataModel():MutableLiveData<MyTestModel>{
//Gets the model from a retrofit service call
objectTypeModel = MyRepository.getModelFromService()
return objectTypeModel
}
}
Am i doing something wrong? already tried convert 'viewModel' into local variable as suggested in other post.
Java Code, MainActivity
MyViewModel model;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
model = new ViewModelProvider(this).get(MyViewModel.class);
model.getUsers().observe(this, new Observer<Integer>() {
#Override
public void onChanged(Integer users) {
Log.d("zzzz","updated value..")
}
});
}
Model
public class MyViewModel extends ViewModel {
private MutableLiveData<Integer> users;
public LiveData<Integer> getUsers() {
if (users == null) {
users = new MutableLiveData<Integer>();
users.setValue(10);
}
return users;
}
}
If you don't want to recreate view model declare your view model like this
private val model: MyViewModel by activityViewModels()
for more details refer ViewModel
I think the issue lies in your kotlin viewmodel class, if you are not getting the value(unless you have few more issues in other classes)
Fix your kotlin viewmodel class in which data is not set in MutableLiveData, you forgot to add a piece of code.
//Here it is like this
objectTypeModel.value= MyRepository.getModelFromService()
AFAIK onCreate() only gets called when activity is created. So its natural if your viewmodel is getting created again. You can also check it by init{} method in your viewmodel class.
Still if you are not satisfied move your api call from activity's onCreate() method to viewmodels init{} method and just observe the changes from Activity. Your getMutableLiveDataModel() will called once when viewmodel object gets created.
If your java viewmodel example is running as you expected. Then,try to convert the java class to kotlin and run it again(just paste the java code to a kotlin file, it will ask you to convert it), it should work.
I've tried the same concept and as expected, the functionality in Java and Kotlin is identical. In the LogCat, I expected that the log should be printed on every rotation and it does. Now, let me tell you why it happens.
So, as per the documentation ViewModel instance stays alive after the configuration change. Basically, ViewModel uses the same instance if your activity is re-creating numerous times but it's not getting destroyed (calling finish()). But it's not the magic of the ViewModel it's the magic of LiveData.
LiveData is an observable data view holder so it sends the latest preserved value to the active observers on every configuration change which you're observing in the onCreate().
Let me present you my code.
Java
// Activity
public class JavaActivity extends AppCompatActivity {
private static final String TAG = "JavaActivity";
private JavaViewModel javaViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_java);
// Ignore this listener
findViewById(R.id.go_to_kotlin_activity).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finish();
}
});
// Main
javaViewModel = new ViewModelProvider(this).get(JavaViewModel.class);
javaViewModel.getJavaLiveData().observe(this, new Observer<Integer>() {
#Override
public void onChanged(Integer integer) {
Log.d(TAG, "onChanged: " + integer);
}
});
}
}
// ViewModel
public class JavaViewModel extends ViewModel {
private MutableLiveData<Integer> javaLiveData;
public LiveData<Integer> getJavaLiveData() {
if(javaLiveData == null) {
javaLiveData = new MutableLiveData<>();
javaLiveData.setValue(10);
}
return javaLiveData;
}
}
Kotlin
// Activity
class KotlinActivity : AppCompatActivity() {
companion object {
private const val TAG = "KotlinActivity"
}
private lateinit var kotlinViewModel: KotlinViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_kotlin)
// Ignore this listener
findViewById<Button>(R.id.go_to_java_activity_btn).setOnClickListener {
startActivity(Intent(this, JavaActivity::class.java))
}
// Main
kotlinViewModel = ViewModelProvider(this).get(KotlinViewModel::class.java)
kotlinViewModel.getKotlinLiveData().observe(this, Observer {
Log.d(TAG, "onCreate: $it")
})
}
}
// ViewModel
class KotlinViewModel : ViewModel() {
private lateinit var kotlinLiveData: MutableLiveData<Int>
fun getKotlinLiveData(): LiveData<Int> {
if (!::kotlinLiveData.isInitialized) {
kotlinLiveData = MutableLiveData()
kotlinLiveData.value = 10
}
return kotlinLiveData
}
}
If you have any follow-up questions, leave them in comments.
Thanks!
References
LiveData - Official Documentation
ViewModel - Official Documentation
This is a great article on how ViewModel works internally.
Do read this article as well
Try
MainActivity
lateinit var viewModel : MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
viewModel.objectTypeModel.observe(this, Observer {
Log.d("zzzz","lamda executes onChanged method -> "+ it.otherValues).
//element from model
})
}
ViewModel
class MyViewModel : ViewModel() {
val objectTypeModel= MutableLiveData<MyTestModel>()
init {
objectTypeModel.value = MyRepository.getModelFromService()
}
}
For example if I this piece of code compiler is suggesting to change to
private val viewListener = object : View.OnClickListener {
override fun onClick(v: View) {
toast("View clicked!$v")
}
}
following code
private val viewListener = View.OnClickListener { v ->
toast("View clicked!$v")
}
but if I have this abstract class
public abstract class DrawableCallback implements Callback {
public void onLoaded() {}
public void onError() {}
public static int extractDrawable() {}
public abstract void onDrawableExtracted(int color);
}
and I use it here
private val drawableCallback = object : DrawableCallback() {
override fun onDrawableExtracted(color: Int) {
//...
}
}
Here it seems I can't convert to lambda , can you explain why?
You cannot use it with abstract classes. Refer to Kotlin Lang documentation (also in comments by Michael). It states clearly:
Note that SAM conversions only work for interfaces, not for abstract
classes, even if those also have just a single abstract method.
. If you need to know more (like in why not?), refer to this discussion Why can't #FunctionalInterface be applied to a SAM abstract base class. This discussion is around Java, but equally applies to Kotlin on JVM for obvious reasons.
I have a situation where I have a view model for a page that is rendered based on some input data (id) with fields that are initialized from my repository but can be edited by the user.
My repository function is pretty straightforward:
class AccountsRepository {
public LiveData<Account> getAccount(long id) {
return roomDb.accountDao().getAccount(id);
}
}
I want my view model to be re-used for different accounts so I have a LiveData<Long> that will be set with the account ID.
class EditAccountViewModel {
private MutableLiveData<Long> accountId = new MutableLiveData<>();
public void setAccountId(long id) {
accountId.setValue(id);
}
}
In my view model, I want to expose a mutable field name that will be bound to an EditText view. This field should be initialized by the data in the repository. If I use a simple non-editable binding, I am able to get one-way databinding working:
class EditAccountViewModel {
private MutableLiveData<Long> accountId = new MutableLiveData<>();
public LiveData<String> name;
EditAccountViewModel() {
this.name = Transformations.map(
Transformations.switchMap(accountId, repo::getAccount),
account -> account.name);
}
}
However, I cannot bind this using #={viewModel.name} because it complains that it does not know how to set the value. I tried writing a helper class like so that uses an underlying MediatorLiveData to set the value, but it looks like my onChanged callback is never called:
class MutableLiveDataWithInitialValue<T> extends MutableLiveData<T> {
MutableLiveDataWithInitialValue(LiveData<T> initialValue) {
MediatorLiveData<T> mediator = new MediatorLiveData<>();
mediator.addSource(
initialValue,
data -> {
// This never gets called per the debugger.
mediator.removeSource(initialValue);
setValue(data);
});
}
}
I updated the view model as follows:
class EditAccountViewModel {
private MutableLiveData<Long> accountId = new MutableLiveData<>();
public MutableLiveData<String> name;
EditAccountViewModel() {
this.name = new MutableLiveDataWithInitialValue<>(
Transformations.map(
Transformations.switchMap(accountId, repo::getAccount),
account -> account.name));
}
}
However, when I do this my EditText field never sets the value from the database, and this makes sense because the onChanged callback is never called in my MediatorLiveData in MutableLiveDataWithInitialValue.
This seems like a pretty common use case, so I'm wondering what I am screwing up?
I was able to workaround this with a very inelegant manner:
class EditAccountViewModel {
private final Executor ioExecutor;
public final MutableLiveData<String> name = new MutableLiveData<>();
public final MutableLiveData<String> someOtherField = new MutableLiveData<>();
public void setAccountId(long id) {
ioExecutor.execute(() -> {
Account account = repo.getAccountSync(id);
name.postValue(account.name);
someOtherField.postValue(account.someOtherField);
});
}
}
This is fine since only the user can edit the fields once they are initialized. But there are obvious race conditions... e.g. if the database takes too long to rade and the user starts typing a value in before then.
Your helper class was a good start. Seems like you've forgotten that LiveData objects (including your MediatorLiveData) aren't triggered unless you observe them - that's why your mediator's onChange() method was never called. If you pass observe/unobserve actions to your mediator it works as intended.
Since you're tying your ViewModel to a view it might be a good idea to provide at least some initial value in the constructor like you would do on the regular MutableLiveData (even if that value is shortly replaced by the one coming from LiveData).
Java:
public class MutableLiveDataWithInitialValue<T> extends MutableLiveData<T> {
private MediatorLiveData<T> mediator = new MediatorLiveData<>();
public MutableLiveDataWithInitialValue(T initialValue, LiveData<T> delayedInitialValue) {
super(initialValue);
mediator.addSource(
delayedInitialValue,
data -> {
mediator.removeSource(delayedInitialValue);
setValue(data);
});
}
#Override
public void observe(#NonNull LifecycleOwner owner, #NonNull Observer<? super T> observer) {
mediator.observe(owner, observer);
super.observe(owner, observer);
}
#Override
public void observeForever(#NonNull Observer<? super T> observer) {
mediator.observeForever(observer);
super.observeForever(observer);
}
#Override
public void removeObserver(#NonNull Observer<? super T> observer) {
mediator.removeObserver(observer);
super.removeObserver(observer);
}
#Override
public void removeObservers(#NonNull LifecycleOwner owner) {
mediator.removeObservers(owner);
super.removeObservers(owner);
}
}
Kotlin:
class MutableLiveDataWithInitialValue<T>(initialValue: T, delayedInitialValue: LiveData<T>) : MutableLiveData<T>(initialValue) {
private val mediator = MediatorLiveData<T>()
init {
mediator.addSource(delayedInitialValue) {
mediator.removeSource(delayedInitialValue)
value = it
}
}
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
mediator.observe(owner, observer)
super.observe(owner, observer)
}
override fun observeForever(observer: Observer<in T>) {
mediator.observeForever(observer)
super.observeForever(observer)
}
override fun removeObserver(observer: Observer<in T>) {
mediator.removeObserver(observer)
super.removeObserver(observer)
}
override fun removeObservers(owner: LifecycleOwner) {
mediator.removeObservers(owner)
super.removeObservers(owner)
}
}
I am trying to call static function of one class in other like java , But in kotlin I can not make a static function , and I have to make a companion object in which I have to define my function , But while doing this I am not able to access parent class variables , is there any way I can achieve this in kotlin .
class One {
val abcList = ArrayList<String>()
companion object {
fun returnString() {
println(abcList[0]) // not able to access abcList here
}
}
}
class Two {
fun tryPrint() {
One.returnString()
}
}
// In Java we can do it like this
class One {
private static ArrayList<String> abcList = new ArrayList<>();
public void tryPrint() {
// assume list is not empty
for(String ab : abcList) {
System.out.println(ab);
}
}
public static void printOnDemand() {
System.out.println(abcList.get(0));
}
}
class Two {
public void tryPrint(){
One.printOnDemand();
}
}
I want to access fun returnString() like static function of class one like we do in java , if any one have achieved this please help .
In your case abcList is a member variable of the class. Each instance of a class has their own version of its member variables which means that a static method cannot access them. If you want to access it from your companion object it has to be static too.
class One {
companion object {
val abcList = ArrayList<String>()
fun returnString() {
println(abcList[0])
}
}
}
class Two {
fun tryPrint() {
One.returnString()
}
}
This code will work, but keep in mind that in this case there will be only one instance of abcList. Accessing a member variable from a static function is not possible (not even in Java).
Here's the Kotlin version of your Java example:
class One {
companion object {
val abcList = ArrayList<String>()
fun printOnDemand() {
println(abcList[0])
}
}
fun tryPrint() {
for (ab in abcList) {
println(ab)
}
}
}
class Two {
fun tryPrint() {
One.printOnDemand()
}
}
Rule: You can't access static properties, members of a class in none static members and you can't access none static properties, members of a class in static members which is the companion object class.
This rule is in both Java and Kotlin. If you want to access a none static member of a class
inside static members you have to declare it inside companion object class.
Use the following code for you case.
object One {
val abcList: MutableList<String> = mutableListOf()
fun returnString() {
println(abcList[0])
}
fun printOnDemand() {
println(abcList[0]);
}
}
class Two {
fun tryPrint() {
One.printOnDemand()
}
}