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.
caesarViewModel
The caesarViewModel
is created as needed. Notice I am using the ViewModelProvider
not ViewModelProviders
.
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...
- Setting the members of
binding
I am setting the lifecycleOwner
and caesarViewModel
. The latter should be a crucial part of the whole DataBinding thing.
- 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
.