kotlinandroid-fragmentsandroid-recyclerviewandroid-roomclicklistener

Kotlin: lateinit var needs to be initialized


I'm struggling with both a dilemma and an issue here. The dilemma is that I want to keep the ManagerUsers screen simple, but Google is pushing fragments. I can see a use for fragments for this screen later on, but for now, it seems like overkill. Should I use them anyway?

Second, if I do avoid fragments for now, I am struggling to finish setting up the ClickListener to simply change the name of the user in the ListView. Kotlin seems to have at least 6 different methods for doing most things, making it confusing to figure this out. Google's tutorials force you to use fragments, and others' tutorials don't really cover what I'm trying to do. When I navigate to the Manage Users activity, the app crashes. LogCat shows that userSelect needs to be initialized. I have no idea how to do that.

UserListAdapter.kt

package com.neillbarrett.debitsandcredits

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.neillbarrett.debitsandcredits.database.UsersTable
import com.neillbarrett.debitsandcredits.databinding.ActivityManageUsersBinding

class UserListAdapter(private val userSelect: (UsersTable?) -> Unit) :
    ListAdapter<UsersTable, UserListAdapter.UserViewHolder>(UsersComparator()) {

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val current = getItem(position)
        holder.bind(current, userSelect)
    }

    class UserViewHolder(private val binding: ActivityManageUsersBinding) : RecyclerView.ViewHolder(binding.root) {

        fun bind(usersTable: UsersTable?, userSelect: (UsersTable?) -> Unit) {

            binding.root.setOnClickListener( View.OnClickListener {
                userSelect(usersTable)
                binding.etEditName.setText(R.layout.activity_list_of_users.toString())
            })
        }

        companion object {
            fun create(parent: ViewGroup) : UserViewHolder {
                val view: View = LayoutInflater.from(parent.context)
                    .inflate(R.layout.activity_manage_users, parent, false)
                return UserViewHolder(ActivityManageUsersBinding.bind(view))
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.activity_manage_users, parent, false)
        return UserViewHolder.create(parent)
    }

    class UsersComparator : DiffUtil.ItemCallback<UsersTable>() {
        override fun areItemsTheSame(oldItem: UsersTable, newItem: UsersTable): Boolean {
            return oldItem == newItem
        }

        override fun areContentsTheSame(oldItem: UsersTable, newItem: UsersTable): Boolean {
            return oldItem.userName == newItem.userName
        }
    }
}

ManageUsers.kt

package com.neillbarrett.debitsandcredits

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.TextUtils
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.activity.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.neillbarrett.debitsandcredits.database.CreditsAndDebitsApp
import com.neillbarrett.debitsandcredits.database.UsersTable
import com.neillbarrett.debitsandcredits.databinding.ActivityManageUsersBinding

class ManageUsers : AppCompatActivity() {

    lateinit var binding: ActivityManageUsersBinding
    lateinit var recyclerView: RecyclerView
    lateinit var editTextAddUser: EditText
    lateinit var editTextChangeUser: EditText
    lateinit var newUser: String
    lateinit var userSelect: ((UsersTable?) -> Unit)
    var position: Long = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityManageUsersBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
        //setContentView(R.layout.activity_manage_users)

        val userViewModel: UserViewModel by viewModels {
            UserViewModelFactory((application as CreditsAndDebitsApp).repository)
        }

        recyclerView = findViewById(R.id.rec_view_userList)
        editTextAddUser = findViewById(R.id.et_UserName)
        editTextChangeUser = findViewById(R.id.et_Edit_Name)

        val adapter = UserListAdapter(userSelect)
        binding.recViewUserList.adapter = adapter
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(this)

        userViewModel.allUsers.observe(this, Observer() {user ->
            user?.let { adapter.submitList(it) }
        })

        val btnAddUser = findViewById<Button>(R.id.btn_AddUser)
        btnAddUser.setOnClickListener {
            if (TextUtils.isEmpty(editTextAddUser.text)) {
                Toast.makeText(this, "User name cannot be empty", Toast.LENGTH_SHORT).show()
            } else {
                newUser = editTextAddUser.text.toString()
                userViewModel.insertUser(UsersTable(0, newUser))
            }
        }

        val btnChangeUser = findViewById<Button>(R.id.btn_ChangeUserName)
        btnChangeUser.setOnClickListener {
            if (recyclerView.getChildAdapterPosition(it) == -1) {
                Toast.makeText(this, "Select a name.", Toast.LENGTH_SHORT).show()
            } else {
                if (editTextChangeUser.text.toString() == recyclerView.adapter.toString()) {
                    Toast.makeText(this, "Name has not been changed.", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(this, "Name would have been changed.", Toast.LENGTH_SHORT).show()
                    /*val rvItemRecId: Long
                    rvItemRecId = adapter.getItemId(position.toInt())
                    userViewModel.updateUser(UsersTable(rvItemRecId.toInt(), adapter.toString()))*/
                }
            }
        }
    }
}

UserViewModel.kt

package com.neillbarrett.debitsandcredits

import androidx.lifecycle.*
import com.neillbarrett.debitsandcredits.database.UsersTable
import kotlinx.coroutines.launch
import java.lang.IllegalArgumentException

class UserViewModel(private val repository: UserRepository) : ViewModel() {

    val allUsers: LiveData<List<UsersTable>> = repository.allUsers.asLiveData()

    fun insertUser(user: UsersTable) = viewModelScope.launch {
        repository.insertUser(user)
    }

    fun updateUser(user: UsersTable) = viewModelScope.launch {
        repository.updateUser(user)
    }

    fun deleteUser(user: UsersTable) = viewModelScope.launch {
        repository.deleteUser(user)
    }
}

class UserViewModelFactory(private val repository: UserRepository) : ViewModelProvider.Factory{
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(UserViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return UserViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

Solution

  • If you check your ManageUsers activity implementation

    lateinit var userSelect: ((UsersTable?) -> Unit)
    

    userSelect is lateinit and it must be initialised before you pass the reference to UserListAdapter adapter. Something like this

    userSelect = {
            // do something
        }
    

    before passing to adapter, or

    val userSelect: ((UsersTable?) -> Unit) = {
         
    }
    

    at the time of declaration.