1

I am trying to communicate with viewmodel from xml and vice versa using MVVM pattern. I have worked on databinding before and successfully worked with Live Data - Dagger - MVVM . Recently, I have tried to create a new project and since then I cannot track the response with XML and viewmodel. Neither the onClick from XML -> ViewModel nor the assigning value to the textview from View -> XML is working. But there is no crash or anything, just it is not working. I have added all associated files [ MainActivity, activity_main, viewModel, Dagger Module, build.gradle ]

I will really appreciate if anyone can tell me what is going wrong here.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>
    <variable
        name="viewmodel"
        type="aveek.com.vm.ui.home.MainActivityViewModel"/>
</data>
<android.support.constraint.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

 <TextView

        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{viewmodel.balanceText}"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:clickable="true"
        android:onClick="@{() -> viewmodel.clickData()}"
        app:layout_constraintTop_toTopOf="parent" />
...
</layout>

MainActivity.kt

class MainActivity : AppCompatActivity(),LifecycleOwner {

@Inject
lateinit var binding : ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
    AndroidInjection.inject(this)
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    binding.setLifecycleOwner(this) 
    with(binding){
        this.viewmodel?.let {
            it.balanceText.set( "Aveek testing")
            it.data.observe(this@MainActivity, Observer {
                Toast.makeText(this@MainActivity, "Data is now : $it", Toast.LENGTH_SHORT).show()
            })
           }
         }
       }

MainActivityViewModel.kt

class MainActivityViewModel : ViewModel() {
val data = MutableLiveData<Boolean>()
val balanceText = ObservableField<String>()
 fun clickData(){
    data.value = false
 }
}

MainActivityModule.kt

@Module
class  MainActivityModule{

/**
 * provides binding to  Main Activity from respective XML
 * @property viewModel
 * @property context
 * @return binding of the view
 */
@Provides
fun binding(context: MainActivity, viewModel : MainActivityViewModel) : 
ActivityMainBinding {
    val binding = DataBindingUtil.setContentView<ActivityMainBinding>(context, 
    R.layout.activity_main)
    binding.viewmodel = viewModel
    return binding
 }
}

build.gradle

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: 'kotlin-kapt'

android {
   compileSdkVersion 27
   defaultConfig {
    applicationId "aveek.test"
    minSdkVersion 15
    targetSdkVersion 27
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    vectorDrawables.useSupportLibrary = true
   }
   dataBinding {
    enabled = true
   }
   buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 
     'proguard-rules.pro'
    }
    }
    compileOptions {
     sourceCompatibility JavaVersion.VERSION_1_8
     targetCompatibility JavaVersion.VERSION_1_8
    }
   }

kapt {
  generateStubs = true
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:multidex:1.0.2'
implementation 'com.android.support:design:27.1.1'
implementation "android.arch.lifecycle:extensions:1.1.0"
//    annotationProcessor "android.arch.lifecycle:compiler:1.1.0"

kapt "com.android.databinding:compiler:$androidPluginVersion"

annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
annotationProcessor "com.google.dagger:dagger-android-processor:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
kapt "com.google.dagger:dagger-android-processor:$daggerVersion"


implementation "com.google.dagger:dagger:$daggerVersion"
implementation "com.google.dagger:dagger-android:$daggerVersion"
implementation "com.google.dagger:dagger-android-support:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"

testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso- 
core:3.0.2'

}


ext{
    kotlin_version = '1.2.31'
    androidPluginVersion = '3.1.0'
    daggerVersion = '2.13'
}
2
  • where you define viewmodel in MainActivity? Commented Mar 28, 2019 at 9:18
  • 1
    I have injected viewmodel in module with the binding. you can check "MainActivityModule.kt" I have attached above for reference. Commented Mar 28, 2019 at 9:27

2 Answers 2

3

you won't be able to use any generated methods to set your variable to the binding, as there's no common superclass besides the ViewDataBinding,so you will be forced to use reflection, or you can use the convenience method setVariable():

binding.setVariable(BR.viewModel, viewModel);
Sign up to request clarification or add additional context in comments.

4 Comments

Tried that, still same issue occurring. I am wondering if my implementation have any issue with dagger or not.
try after adding binding.executePendingBindings() line also
Now it should work, I think you also tried but just to say try after clean project.
It is fine without DI, the viewmodel and its behaviour is working good. I have added my answer, you can have a look.
0

As I suspected, the problem is with Dagger injection. Maybe I have implemented in a wrong approach but the viewmodel is perfectly fine if running without DI.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    viewModel = ViewModelProviders.of(this).get(MainActivityViewModel::class.java)
    binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
    binding.viewmodel = viewModel

    binding.setLifecycleOwner(this)
    mLifecycleRegistry = LifecycleRegistry(this).apply {
        markState(Lifecycle.State.CREATED)
    }
    with(binding){
        this.viewmodel?.let {
            it.balanceText.set( "Aveek testing")
            it.data.observe(this@MainActivity, Observer {
                Toast.makeText(this@MainActivity, "Data is now : $it", 
          Toast.LENGTH_SHORT).show()
            })
        }
    }
}

So, I have gone through the DI code and found out that in MainActivity.onCreate() I was doing this

AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

which basically overriding the databinding behaviour. So I have updated the code like this

AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
//setContentView(R.layout.activity_main)
// and initiated binding here as injecting binding from module before setting content won't be effective
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

And removed

//    @Inject
//    lateinit var binding : ActivityMainBinding

Now it works perfectly fine..

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.