kotlin.UninitializedPropertyAccessException: latei

2020-03-30 04:45发布

问题:

I am trying to follow Pusher Chatkit's tutorial for "BUILDING A GROUP CHAT APP USING KOTLIN AND PUSHER CHATKIT" however I am running into an issue with the Recycler View adapter. FYI, I am still learning Kotlin. I've been reading up on lateinit vars but I can't find anything that addresses this case. The error occurs in the recycler view adapter.

This is the error I get:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.android_myneighborsbookshelf, PID: 26692
    kotlin.UninitializedPropertyAccessException: lateinit property roomClickedInterface has not been initialized
        at com.example.android_myneighborsbookshelf.adapters.ChatRoomsListAdapter.getRoomClickedInterface(ChatRoomsListAdapter.kt:13)
        at com.example.android_myneighborsbookshelf.adapters.ChatRoomsListAdapter$ViewHolder.onClick(ChatRoomsListAdapter.kt:66)
        at android.view.View.performClick(View.java:6597)
        at android.view.View.performClickInternal(View.java:6574)
        at android.view.View.access$3100(View.java:778)
        at android.view.View$PerformClick.run(View.java:25885)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

ChatRoomsListActivity.kt

class ChatRoomsListActivity : AppCompatActivity() {
    val adapter = ChatRoomsListAdapter();

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_chat_room_list)
        initRecyclerView()
        initChatManager()
    }

    private fun initRecyclerView() {
        recycler_view.layoutManager = LinearLayoutManager(this@ChatRoomsListActivity)
        recycler_view.adapter = adapter
    }

    private fun initChatManager() {
        val chatManager = ChatManager(
                instanceLocator = "blahblahblah",
                userId = "username1-PCKid",
                dependencies = AndroidChatkitDependencies(
                        tokenProvider = ChatkitTokenProvider(
                                endpoint = "blahblahblah",
//                                endpoint = "http://10.0.2.2:3000/auth",
                                userId = "username1-PCKid"
                        )
                )
        )

        chatManager.connect(listeners = ChatListeners(
                onErrorOccurred = { },
                onAddedToRoom = { },
                onRemovedFromRoom = { },
                onCurrentUserReceived = { },
                onNewReadCursor = { },
                onRoomDeleted = { },
                onRoomUpdated = { },
                onPresenceChanged = { u, n, p -> },
                onUserJoinedRoom = { u, r -> },
                onUserLeftRoom = { u, r -> },
                onUserStartedTyping = { u, r -> },
                onUserStoppedTyping = { u, r -> }
        )) { result ->
            when (result) {
                is Result.Success -> {
                    // We have connected!
                    val currentUser = result.value
                    AppController.currentUser = currentUser
                    val userJoinedRooms = ArrayList<Room>(currentUser.rooms)
                    for (i in 0 until userJoinedRooms.size) {
                        adapter.addRoom(userJoinedRooms[i])
                    }

                    currentUser.getJoinableRooms { result ->
                        when (result) {
                            is Result.Success -> {
                                // Do something with List<Room>
                                val rooms = result.value
                                runOnUiThread {
                                    for (i in 0 until rooms.size) {
                                        adapter.addRoom(rooms[i])
                                    }
                                }
                            }
                        }
                    }

                    adapter.setInterface(object : ChatRoomsListAdapter.RoomClickedInterface {
                        override fun roomSelected(room: Room) {
                            if (room.memberUserIds.contains(currentUser.id)) {
                                // user already belongs to this room
                                roomJoined(room)
                            } else {
                                currentUser.joinRoom(
                                        roomId = room.id,
                                        callback = { result ->
                                            when (result) {
                                                is Result.Success -> {
                                                    // Joined the room!
                                                    roomJoined(result.value)
                                                }
                                                is Result.Failure -> {
                                                    Log.d("TAG", result.error.toString())
                                                }
                                            }
                                        }
                                )
                            }
                        }
                    })
                }

                is Result.Failure -> {
                    // Failure
                    Log.d("TAG", result.error.toString())
                }
            }
        }
    }

    private fun roomJoined(room: Room) {
        val intent = Intent(this@ChatRoomsListActivity, ChatRoomsListActivity::class.java)
        intent.putExtra("room_id", room.id)
        intent.putExtra("room_name", room.name)
        startActivity(intent)
    }
}

RecyclerView Adapter

import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.pusher.chatkit.rooms.Room
import android.view.LayoutInflater
import androidx.recyclerview.widget.RecyclerView
import com.example.android_myneighborsbookshelf.R

class ChatRoomsListAdapter: RecyclerView.Adapter<ChatRoomsListAdapter.ViewHolder>() {
    private var list = ArrayList<Room>()
    lateinit var roomClickedInterface:RoomClickedInterface // <-- Error occurs here - kt.13

    fun addRoom(room:Room){
        list.add(room)
        notifyDataSetChanged()
    }

    fun setInterface(roomClickedInterface:RoomClickedInterface){
        this.roomClickedInterface = roomClickedInterface
    }

    override fun getItemCount(): Int {
        return list.size
    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context)
                .inflate(
                        android.R.layout.simple_list_item_1,
                        parent,
                        false
                )

        return ViewHolder(view)
    }


    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.roomName.text = list[position].name

    }

    inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView), View.OnClickListener {
        override fun onClick(p0: View?) {
            roomClickedInterface.roomSelected(list[adapterPosition]) // <-- Error occurs here kt.66
        }

        var roomName: TextView = itemView.findViewById(android.R.id.text1)

        init {
            itemView.setOnClickListener(this)
        }
    }

    interface RoomClickedInterface{
        fun roomSelected(room:Room)
    }
}

Any and all help is appreciated.

回答1:

lateinit means late initialization. If you do not want to initialize a variable in the constructor, instead you want to initialize it later on and if you can guarantee the initialization before using it, then declare that variable with lateinit keyword. It will not allocate memory until initialized.

So, You have to initialize the lateinit property before try to use it.

Option - 1: Call setInterface() to initialize the property before click the list item. You can also check whether a lateinit var has already been initialized or not using .isInitialized like below:

override fun onClick(p0: View?) {

    if(this::roomClickedInterface.isInitialized) {
        roomClickedInterface.roomSelected(list[adapterPosition])
    }
}

Option - 2: Remove lateinit property from your adapter and also it's setter

//lateinit var roomClickedInterface:RoomClickedInterface

And pass RoomClickedInterface as parameter of your adapter's constructor

class ChatRoomsListAdapter(val roomClickedInterface:RoomClickedInterface):
     RecyclerView.Adapter<ChatRoomsListAdapter.ViewHolder>() {

    //Implementation
}

Option - 3: Remove lateinit and use nullable property

private var roomClickedInterface: RoomClickedInterface? = null

And then use like below using null safety notation ?.:

override fun onClick(p0: View?) {
    roomClickedInterface?.roomSelected(list[adapterPosition])
}