Can not communicate with XML from ViewModel using Data binding Android - android

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'
}

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);

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..

Related

Cannot create an instance of viewmodel class with hilt

I am trying to create MVVM app using Openweathermap API and hilt. I tried to inject my repository into my viewmodel primary constractor and creating a ViewModelFactory class, in order to pass the parameters from viewmodel class to my main activity class it did not work with 'by viewmodels()' itself. Sadly it did not work and I am getting the following message "has no zero argument constructor". It worth to mention that I also tried to inject the repository into my secondary constractor.
This is my MainActivity
#AndroidEntryPoint
class MainActivity
#Inject constructor(var repository:WeatherRepositoryInterface) : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var previewAdapter: PreviewAdapter
lateinit var viewModel:WeatherViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initViewItems()
}
fun initViewItems() {
viewModel= ViewModelProvider(this#MainActivity,FactoryViewModel(application,repository))[WeatherViewModel::class.java]
binding.recyclerview.apply {
layoutManager = LinearLayoutManager(this#MainActivity)
previewAdapter = PreviewAdapter(viewModel.loadCities().value!!)
previewAdapter.let {
it.setOnItemClickListener(object: OnClickInterface{
override fun onClickItem(position: Int) {
var intent=Intent(this#MainActivity,DetailActivity::class.java)
intent.putExtra("name",viewModel.loadCities().value!![position].cityName)
intent.putExtra("img",viewModel.loadCities().value!![position].cityImg)
startActivity(intent)
}
})
}
adapter = previewAdapter
}
}
}
My ViewModel class
#HiltViewModel
class WeatherViewModel #Inject constructor(application: Application, var repository: WeatherRepositoryInterface)
: AndroidViewModel(application) ,LifecycleObserver{
companion object{
private val VIEWMODEL_STRING="WeatherViewModel.class"
}
private var list: MutableLiveData<List<Preview>> = MutableLiveData()
fun getCityInfo(q:String) =
liveData(Dispatchers.IO){
emit(com.example.yourweatherapp.Resources.Resource.loading(data = null))
try {
emit(com.example.yourweatherapp.Resources.Resource.success(data = repository.getWeather(q=q)))
} catch (e: Exception) {
emit(e.message?.let { com.example.yourweatherapp.Resources.Resource.error(data = null, message = it) })
e.message?.let { Log.e(VIEWMODEL_STRING, it) }
}
}
fun loadCities():MutableLiveData<List<Preview>>{
list.value= listOf(
Preview(
CityList.santorini,
CityList.santoriniImg),
Preview(CityList.bern,
CityList.bernImg),
Preview(CityList.venice,
CityList.veniceImg),
Preview("",CityList.myLocationImg)
)
return list
}
}
My ViewModelFactory class:
class FactoryViewModel(
var application:Application,
var repository: WeatherRepositoryInterface
): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T =
WeatherViewModel(application,repository) as T
}
My Gradle dependency file:
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}
android {
compileSdk 31
defaultConfig {
applicationId "com.example.yourweatherapp"
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
dataBinding = true
viewBinding = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.4.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation("androidx.cardview:cardview:1.0.0")
implementation 'com.github.bumptech.glide:glide:4.12.0'
implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.recyclerview:recyclerview:1.2.1")
// implementation ("androidx.lifecycle:lifecycle-livedata:2.5.0-alpha01")
//http3
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3"))
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.0-alpha01"
implementation "androidx.activity:activity-ktx:1.1.0"
implementation("com.squareup.okhttp3:okhttp")
implementation("com.squareup.okhttp3:logging-interceptor")
//lifecycle
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0")
kapt("androidx.lifecycle:lifecycle-compiler:2.5.0-alpha01")
implementation("androidx.lifecycle:lifecycle-common-java8:2.5.0-alpha01")
implementation("androidx.lifecycle:lifecycle-service:2.5.0-alpha01")
//fragment
implementation("androidx.fragment:fragment-ktx:1.4.1")
implementation("androidx.navigation:navigation-fragment-ktx:2.4.1")
implementation("androidx.navigation:navigation-ui-ktx:2.4.1")
//hilt dagger
implementation("com.google.dagger:hilt-android:2.38.1")
kapt("com.google.dagger:hilt-android-compiler:2.38.1")
//implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2"
implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'
implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
}
kapt {
javacOptions {
// These options are normally set automatically via the Hilt Gradle plugin, but we
// set them manually to workaround a bug in the Kotlin 1.5.20
option("-Adagger.fastInit=ENABLED")
option("-Adagger.hilt.android.internal.disableAndroidSuperclassValidation=true")
}
}
Just remove this line from your dependencies:
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
I found this in migrations doc and solved the issue for my case:
Remove this line from gradle:
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
I'm using latest versions of all dependencies, make sure to check and apply all related and necessary migrations

Hilt ViewModel constructor not working when navigation component scope

It is my first time when I am playing around with Hilt. I have created a demo app with BottomNavigationView. I have added #AndroidEntryPoint and #HiltAndroidApp. I created a Module class and injected the ViewModel but I am still getting the same error over and over again.
Class <ViewModel> has no zero argument constructor
Some code:
#HiltAndroidApp
class MyApplication: Application() {
}
<application
android:name=".MyApplication"
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navView: BottomNavigationView = findViewById(R.id.nav_view)
val navController = findNavController(R.id.nav_host_fragment)
navView.setupWithNavController(navController)
}
}
#AndroidEntryPoint
class CampaignsFragment : Fragment() {
private var _binding: FragmentCampaignsBinding? = null
private val binding get() = _binding!!
private val campaignsViewModel: CampaignsViewModel by navGraphViewModels(R.id.mobile_navigation) {
defaultViewModelProviderFactory
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentCampaignsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
campaignsViewModel.getCampaigns()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
class CampaignsViewModel #ViewModelInject constructor(
private val db: FirebaseFirestore,
#Assisted private val savedStateHandle: SavedStateHandle
): ViewModel() {
fun getCampaigns() {
db.collection("campaigns")
.get()
.addOnSuccessListener {
for (document in it) {
Log.d(BuildConfig.BUILD_TYPE, "${document.id} => ${document.data}")
}
}
.addOnFailureListener {
}
}
}
#Module
#InstallIn(FragmentComponent::class)
class ApplicationModule {
#Provides
fun provideFirebaseFirestore(): FirebaseFirestore {
return Firebase.firestore
}
}
I have added all the code I am using. Am I missing something? I couldn't find any example or missing errors. I did read the hilt documentation about ViewModel and the implementation.
app gradle file content:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.gms.google-services'
id 'kotlin-android-extensions'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "ro.marianpavel.partidulverde"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
buildFeatures {
viewBinding true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
implementation platform('com.google.firebase:firebase-bom:25.12.0')
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation 'com.google.firebase:firebase-firestore-ktx'
testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation "com.google.dagger:hilt-android:2.29-alpha"
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02'
kapt "com.google.dagger:hilt-android-compiler:2.29-alpha"
}
I think this might have something to do with the way you provide your firebase dependency. With FragmentComponent::class your ViewModel can't access this dependency. This should work when changing it with ApplicationComponent::class.
Another problem could be that you are missing kapt "com.google.dagger:hilt-android-compiler:$dagger_hilt_version" as well as kapt "androidx.hilt:hilt-compiler:$dagger_hilt_lc_version" in your gradle

Can't fix error: Cannot find symbol class ActivityMainBindingImpl

I've got following error while trying to build prject with MVVM and data binding. I've searched through everything I could find here on StackOverflow, or common mistakes on the internet, but nothing worked for my case.
The only message I receive is this error. It looks like this in buildOutput:
I've had my packages named with capital letters and I've found here that this might be the cause because compiler treat them as names of classes, so I've changed them all to start with small letters, but that haven't helped.
I've created ViewModelFactory for creating my ViewModel inside Activity so I could send additional parameters with constructor using factory, so I've tried to remove it and use no parameters and create instance without using factory for this purpose, but still I havn't got any results (same error)
I was chaning both build.gradle's in different ways but result was always the same.
Finally I've deleted data binding and variable from XML, then I was able to run the app (with other errors, but I would be probably able to deal with those by myself) but I want to leave it as it is and just deal with my error.
I am not experienced with MVVM and data binding so it can be just a stupid mistake, but it is hard to find it if I don't know where I should look for it.
Here I post most important codes, if you need more then let me know:
build.grale(app)
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 30
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.example.rickmorty"
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
buildFeatures{
dataBinding = true
viewBinding = true
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.3.0-alpha02'
implementation "android.arch.lifecycle:extensions:1.1.1"
implementation "androidx.cardview:cardview:1.0.0"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'io.reactivex.rxjava2:rxjava:2.1.7'
implementation 'com.github.bumptech.glide:glide:4.6.1'
kapt 'com.github.bumptech.glide:compiler:4.4.0'
}
**build.gradle(project)
buildscript {
ext.kotlin_version = "1.3.72"
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var mainViewModel: MainViewModel
lateinit var mAdapter: CharactersAdapter
lateinit var api: CharacterAPI
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val retrofit = RetrofitClient.instance
api = retrofit.create(CharacterAPI::class.java)
setupViewModel()
setupRecycler()
mainViewModel.getData().observe(this,
Observer<List<Results>> { t ->
mAdapter = CharactersAdapter(this#MainActivity, t!!)
rvCharacters.adapter = mAdapter
})
}
fun setupRecycler() {
val lManager = LinearLayoutManager(this#MainActivity)
rvCharacters.apply {
setHasFixedSize(true)
layoutManager = lManager
}
rvCharacters.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val visibleItems = lManager.childCount
val totalItems = lManager.itemCount
val firstVisible = lManager.findFirstVisibleItemPosition()
if (dy > 0) {
if (!mainViewModel.isLoading.value!! && (visibleItems + firstVisible) >= totalItems) {
mainViewModel.scrolledNext()
}
} else {
if (!mainViewModel.isLoading.value!! && (totalItems - visibleItems) <= 0) {
mainViewModel.scrolledPrev()
}
}
}
})
}
fun setupViewModel() {
mainViewModel = ViewModelProviders.of(this, MainViewModelFactory(application, api))
.get(MainViewModel::class.java)
DataBindingUtil.setContentView<ActivityMainBinding>(
this, R.layout.activity_main
).apply {
lifecycleOwner = this#MainActivity
viewmodel = mainViewModel
}
}
}
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="com.example.rickmorty.ViewModel.MainViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<RelativeLayout
android:id="#+id/rlPageTitleHolder"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHeight_percent="0.1">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp"
android:layout_centerInParent="true"
android:text="#{() -> viewmodel.pageNumber}"/>
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvCharacters"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="#id/rlPageTitleHolder"
app:layout_constraintHeight_percent="0.9"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
As I wrote earlier, after removing variable from XML and binding from MainActivity error disappears.
I know it is a lot of code, so if something is redundant just let me know. There is factory class still missing, but I will post it if it's necessary. Also MainRepo is one I haven't attached here, but it's quite long, but I can post it all if you're gonna need it.
Here is the problem --> android:text="#{() -> viewmodel.pageNumber}"
correct syntax for assigning value using dataBinding is
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp"
android:layout_centerInParent="true"
android:text="#{viewmodel.pageNumber}"/>

Integrate Library module built with Kotlin Coroutines dependency issue

I am building a library module that using coroutines.
My library do the following:
get config from the app module
create a fragment that implemented CoroutineScope (ContentListingFragment)
ContentListingFragment handle all it's process fetching data from the network and show them
from the app module:
We should able to get an instance from the ContentListingFragment and add it to a container
The issue: when I am building the app, I am getting the following error:
Supertypes of the following classes cannot be resolved. Please make sure you have the required dependencies in the classpath:
class nabed.apps.nabedutilslibrary.ui.base.ContentListingFragment, unresolved supertypes: kotlinx.coroutines.CoroutineScope
below is the library module build.gradle
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: "kotlin-kapt"
apply plugin: 'androidx.navigation.safeargs'
android {
compileSdkVersion 28
buildToolsVersion "29.0.0"
defaultConfig {
minSdkVersion 21
targetSdkVersion 28
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
androidExtensions{
experimental = true
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
// Navigation
implementation "android.arch.navigation:navigation-fragment:$navigation_version"
implementation "android.arch.navigation:navigation-ui:$navigation_version"
implementation "android.arch.navigation:navigation-fragment-ktx:$navigation_version"
implementation "android.arch.navigation:navigation-ui-ktx:$navigation_version"
//Kotlin Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0-RC1"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
How to handle this without changes on the app module?
You have to change the dependencies declaration in your library's build.gradle:
//Kotlin Coroutines
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0-RC1"
api keyword will expose these dependencies to the app module
****put this code in build.gradel file in dependencies block and click on sync****
//Kotlin Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0"
If you are not going to use Coroutines in the app module but would like to use it in the library, decision with api dependencies not the best one. In this way your app module will be also dependent on Coroutines.
I think, better to remove CoroutineScope implementation from ContentListingFragment and create CoroutineScope object as a ContentListingFragment member.
class ContentListingFragment : Fragment() {
private val job = Job()
private val coroutineScope = object : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.IO
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
coroutineScope.launch { ... }
}
override fun onDestroyView() {
super.onDestroy()
job.cancelChildren()
}
}
Also, LifecycleScope form lifecycle-runtime-ktx may be a the easiest solution. It will remove CoroutineScope creation boilerplate code.
class ContentListingFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch { ... }
}
}

Why is my observable never seeing the update to my list within a kotlin android coroutine?

I'm trying to build an upload application modeled off this tutorial: https://codelabs.developers.google.com/codelabs/kotlin-coroutines/#4
I'm setting a breakpoint on line 29 (where I've commented "I never reach this breakpoint. Why not?"). Why don't I reach this breakpoint when I click on the button?
MainActivity.kt
package com.example.uploadwithprogresssimple
import android.arch.lifecycle.*
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.constraint.ConstraintLayout
import android.support.design.widget.Snackbar
import android.widget.TextView
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewModel = ViewModelProviders.of(this)
.get(MainViewModel::class.java)
val scheduler: TextView = findViewById(R.id.scheduleUpload)
val rootLayout: ConstraintLayout = findViewById(R.id.rootLayout)
scheduler.setOnClickListener {
viewModel.uploadVideo("/foobar/abc/def")
}
viewModel.status.observe( this, Observer {
// I never reach this breakpoint. Why not?
if (it != null) {
var size = it.size
Snackbar.make(rootLayout, "Size of video upload: ${size}", Snackbar.LENGTH_SHORT).show()
}
})
}
}
data class VideoAsset(private val filename: String)
class MainViewModel : ViewModel() {
private val viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private val _status = MutableLiveData<ArrayList<VideoAsset>>()
val status: LiveData<ArrayList<VideoAsset>>
get() = _status
override fun onCleared() {
super.onCleared()
uiScope.cancel()
}
fun uploadVideo(filename: String) {
uiScope.launch {
delay(1_000)
_status.value?.add( VideoAsset( filename) )
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/rootLayout"
tools:context=".MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Schedule upload"
android:id="#+id/scheduleUpload"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
The app/build.gradle file:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.uploadwithprogresssimple"
minSdkVersion 23
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
def kotlinCoroutines = "1.1.0"
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
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'
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines"
}
In this case breakpoint is not reached because setValue(T) method of LiveData object is not called. Calling setValue(T) results in the observers calling their onChanged() method. Try to change your code to call setValue(T) method, i.e.:
val list = _status.value ?: arrayListOf<VideoAsset>()
list.add(VideoAsset(filename))
_status.value = list
Note: setValue(T) in Kotlin is replaced with property assigning: _status.value = list instead of _status.setValue(list)

Categories

Resources