DataBinding error in multiplatform app using Kotlin Multiplatform Mobile (KMM)

700 views Asked by At

I'm new in Kotlin and I'm facing problem with DataBinding - Cannot access class ViewModel. Check your module classpath for missing or conflicting dependencies. But DataBinding is connect in block buildFeatures

My build.gradle.kts(androidApp):

    plugins {
    id("com.android.application")
    kotlin("android")
    id("kotlin-android")
    id("com.squareup.sqldelight")
    id("kotlinx-serialization")
    id("kotlin-kapt")
}
android {
    compileSdkVersion(30)
    defaultConfig {
        applicationId = "com.rompos.activator.kmm.androidApp"
        minSdkVersion(26)
        targetSdkVersion(30)
        versionCode = 1
        versionName = "1.0"
    }
    buildTypes {
        getByName("release") {
            isMinifyEnabled = false
        }
    }
    buildFeatures {
        viewBinding = true
        dataBinding = true
    }
    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }
    packagingOptions {
        pickFirst("META-INF/*.kotlin_module")
    }
    kapt {
        generateStubs = true
        correctErrorTypes = true
    }
}

dependencies {
    implementation(project(":shared"))
    implementation("com.google.android.material:material:1.2.1")

    implementation("androidx.core:core-ktx:1.3.2")
    implementation("androidx.appcompat:appcompat:1.2.0")
    implementation("androidx.constraintlayout:constraintlayout:2.0.4")
    implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
    implementation("androidx.recyclerview:recyclerview:1.1.0")
    implementation("androidx.legacy:legacy-support-v4:1.0.0")
    implementation("androidx.activity:activity-ktx:1.1.0")
    implementation("androidx.fragment:fragment-ktx:1.2.5")

    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1")

    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.2.0")
    implementation("androidx.lifecycle:lifecycle-common-java8:2.2.0")
    implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0")
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.2.0")

    implementation("com.squareup.sqldelight:android-driver:1.4.4")
    implementation("org.kodein.di:kodein-di:7.1.0")
}

Fragment where I got error:

const val EDIT_MODEL = "editModel"
const val EDIT_MODEL_ID = "editModelId"

open class EditServerFragment : Fragment() {
    private var _viewBinding: FragmentEditServerBinding? = null
    private val viewBinding get() = _viewBinding!!

    private val repository: ServersRepository by myApp.kodein.instance()
    private var serverFormViewModel = ServerFormViewModel()
    private var serverId: Long = 0

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _viewBinding = FragmentEditServerBinding.inflate(inflater, container, false)
        val view = viewBinding.root

        val binding : FragmentEditServerBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_edit_server, container, false)
        (Error here ->)**binding.item** = serverFormViewModel

        // Dispatcher Back Step to Main
        activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                toMain()
            }
        })

        if (arguments?.getLong("ID") != null) {
            serverId = arguments?.getLong("ID")!!
        }

        // Set title
        if (activity is AppCompatActivity) {
            (activity as AppCompatActivity).supportActionBar?.title = getString(R.string.edit_server)
        }

        if (serverId > 0) {
            lifecycleScope.launch {
                viewBinding.progressBar.visibility = View.VISIBLE
                repository.get(serverId).let { server ->
                    serverFormViewModel.setForm(server)
                }
            }.also {
                viewBinding.progressBar.visibility = View.GONE
            }
        }

        (Error here ->)**binding.item** = serverFormViewModel

        viewBinding.cancelBtn.setOnClickListener {
            toMain()
        }

        viewBinding.saveBtn.setOnClickListener {
            if (serverFormViewModel.isFormValid()) {
                lifecycleScope.launch {
                    saveRecord(serverFormViewModel)
                    requireActivity().run {
                        startActivity(Intent(this, MainActivity::class.java))
                        finish()
                    }
                }.also {
                    toMain()
                }
            } else {
                Utils.snackMsg(view, getString(R.string.error_empty_field))
                println("EMPTY")
            }
        }

        return viewBinding.root
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putLong(EDIT_MODEL_ID, serverId)
        outState.putParcelable(EDIT_MODEL, serverFormViewModel.getModel())
    }

    override fun onViewStateRestored(savedInstanceState: Bundle?) {
        super.onViewStateRestored(savedInstanceState)
        if (savedInstanceState != null) {
            serverId = savedInstanceState.getLong(EDIT_MODEL_ID)
            serverFormViewModel.setForm(savedInstanceState.getParcelable(EDIT_MODEL)!!)
        }
    }

    private fun saveRecord(viewModel: ServerFormViewModel) {
        viewBinding.progressBar.visibility = View.VISIBLE
        try {
            repository.save(serverId, viewModel.getModel(serverId))
            toMain()
        } catch (e: Exception) {
            view?.let { Utils.snackMsg(it, e.message.toString()) }
        } finally {
            viewBinding.progressBar.visibility = View.GONE
        }
    }

    private fun toMain() {
        requireActivity().run {
            startActivity(Intent(this, MainActivity::class.java))
            finish()
        }
    }
}

Layout:

<?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="item"
            type="com.rompos.activator.kmm.shared.model.ServerFormViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/editView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <androidx.appcompat.widget.LinearLayoutCompat
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="8dp"
            android:orientation="vertical"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <com.google.android.material.textfield.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/server_title"
                app:endIconMode="clear_text"
                tools:layout_editor_absoluteY="8dp">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/serverTitle"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:singleLine="true"
                    android:text="@={item.title}" />

            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/server_url"
                app:endIconMode="clear_text">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/serverUrl"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:inputType="textUri"
                    android:singleLine="true"
                    android:text="@={item.url}" />
            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/server_token"
                app:endIconMode="clear_text">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/serverToken"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:singleLine="true"
                    android:text="@={item.token}" />
            </com.google.android.material.textfield.TextInputLayout>

        </androidx.appcompat.widget.LinearLayoutCompat>

        <androidx.appcompat.widget.LinearLayoutCompat
            android:layout_width="match_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            android:gravity="center|bottom"
            android:orientation="horizontal"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="1.0"
            app:layout_constraintStart_toStartOf="parent">

            <Button
                android:id="@+id/cancelBtn"
                style="@style/Widget.AppCompat.Button.Borderless.Colored"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/cancel_btn"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/saveBtn"
                app:layout_constraintStart_toStartOf="parent" />

            <Button
                android:id="@+id/saveBtn"
                style="@style/Widget.AppCompat.Button.Colored"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/save_btn"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent" />


        </androidx.appcompat.widget.LinearLayoutCompat>

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/progressBar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#68898989"
            android:visibility="gone"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <ProgressBar
                style="?android:attr/progressBarStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:backgroundTintMode="add"
                android:progressBackgroundTintMode="multiply"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

ViewModel:

class ServerFormViewModel {
    private var serverModel = ServerViewModel()

    fun setForm(server: Server) {
        serverModel.setModel(server)
    }

    fun setForm(viewModel: ServerViewModel) {
        serverModel = viewModel
    }

    fun getTitle() : String {
        return serverModel.title ?: ""
    }

    fun setTitle(value: String) {
        serverModel.title = value
    }

    fun getUrl() : String {
        return serverModel.url ?: ""
    }

    fun setUrl(value: String) {
        serverModel.url = value
    }

    fun getToken() : String {
        return serverModel.token ?: ""
    }

    fun setToken(value: String) {
        serverModel.token = value
    }

    fun isFormValid() : Boolean {
        return !serverModel.title.isNullOrEmpty() and !serverModel.url.isNullOrEmpty() and !serverModel.token.isNullOrEmpty()
    }

    fun getModel(id: Long?) : Server {
        return Server(id!!, serverModel.title, serverModel.url, serverModel.token)
    }

    fun getModel() : ServerViewModel {
        return serverModel
    }
}

This proj on Git: https://github.com/Diy2210/com.rompos.activator.kmm

1

There are 1 answers

0
Jean Patricio On

I think you need to bind your ViewModel just in onViewCreated method.