0

I am new to Kotlin and Android development. I am aware that there are similar if not even same questions here. Yes, i spent the whole day trying to figure it out.

I am also sorry not providing a proper toy example.

I have a simple goal. I want to have two EditText "linked" together. When I change one, i expect the other to be changed as well. I want to apply the Caesar's cipher (the cipher is implemented and works). But we will ignore the second EditText for now and focus only on the binding of the first.

I have read that the modern way to achieve this is with DataBinding. It sounds reasonable, since I don't have to write that much code.

However, since it is a "new" feature which also changed, many tutorials fail to provide me with the full code example, which i could simply reuse. I tried to implement it myself from the bits and pieces on the web.

THE BINDING DOES NOT WORK!

I have some layout of some fragment (I am leaving out unimportant things)

<layout>
    <data>
        <variable name="caesarViewModel" type="cz.fortescue.ui.caesar.CaesarViewModel"/>
    </data>
    <EditText
         android:id="@+id/caesar_input"
         android:inputType="text"
         android:text="@={caesarViewModel.input}"/>
</layout>

Where the "@={caesarViewModel.input}" is the important part. (don't miss the =).

Now the ViewModel.

class CaesarViewModel : ViewModel() {
    val input = MutableLiveData<String>().apply {
        value = "aaaaaa"
    }
}

I am using MutableLiveData, so the DataBiding will be able to change it. I am also using apply to test the observer, which I will show you later on.

Last but not least the Fragment

class CaesarFragment : Fragment() {
    private lateinit var binding: FragmentCaesarBinding
    private val caesarViewModel by lazy { ViewModelProvider(this).get(CaesarViewModel::class.java) }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentCaesarBinding.inflate(
            inflater,
            container,
            false
        )

        binding.caesarViewModel = caesarViewModel
        binding.lifecycleOwner = viewLifecycleOwner

        val root = inflater.inflate(R.layout.fragment_caesar, container, false)

        caesarViewModel.input.observe(viewLifecycleOwner, Observer { text ->
            caesarViewModel.output.value = encrypt(text) { caesarEncrypt(it, 1)}
            Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
        })
        return root
    }
}

Since I am new to Kotlin, I am half guessing what happens.

  1. caesarViewModel

The caesarViewModel is created as needed. Notice I am using the ViewModelProvider not ViewModelProviders.

  1. binding

There were 5 different ways i found online, how to properly get the binding. I hope this is now the correct one. I suspect here some errors, but I have not found any...

  1. Setting the members of binding

I am setting the lifecycleOwner and caesarViewModel. The latter should be a crucial part of the whole DataBinding thing.

  1. Observer

I create an Observer, which should notify me with a Toast, if anything changes. It does it once with aaaaaa. Since this is the value, I applied to the MutableLiveData I suspect that the observer works just fine.

It seems to be also necessary to change the build.gradle

Plugins:

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: 'kotlin-kapt'

android:

dataBinding {
    enabled = true
}
compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
    jvmTarget = JavaVersion.VERSION_1_8.toString()
}

Dependencies:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.2.0'
    implementation 'com.google.android.material:material:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.navigation:navigation-fragment:2.2.1'
    implementation 'androidx.navigation:navigation-ui:2.2.1'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.2.1'
    implementation 'androidx.navigation:navigation-ui-ktx:2.2.1'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

What am I missing??? There has to be something I have overlooked, or don't know about. In some tutorials, they use the @Bindable decorator, however this should not be necessary with MutableLiveData.

hr0m
  • 2,643
  • 5
  • 28
  • 39
  • "I am new to Kotlin and Android development" -- you might want to start with something simpler and more conventional. Relatively few developers will be using two-way data binding with `LiveData` (if that's even possible). "What am I missing?" -- you could explain exactly what you mean by "THE BINDING DOES NOT WORK!". For example, do you crash? If so, [check Logcat for the stack trace to see what is going wrong](https://stackoverflow.com/questions/23353173/unfortunately-myapp-has-stopped-how-can-i-solve-this). – CommonsWare Apr 04 '20 at 22:25
  • Maybe you have to write a Bindingadapter and InverseBindingadapter for the Edittexts if it doesnt work correct what values are shown.(assuming you already fixed the return value of onCreateView) – Dominik Wuttke Apr 04 '20 at 22:35

1 Answers1

1

from onCreateView return binding.root you are returning wrong root you are returning an inflated view with this code (it is wrong for data binding):

val root = inflater.inflate(R.layout.fragment_caesar, container, false)

you should replace the above line from onCreateView with:

return binding.root
ygngy
  • 3,630
  • 2
  • 18
  • 29