"Not enough information to infer parameter T" with Kotlin and Android

I'm trying to replicate the following ListView in my Android app using Kotlin: https://github.com/bidrohi/KotlinListView.

Unfortunately I'm getting an error I'm unable to resolve myself. Here's my code:

MainActivity.kt:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val listView = findViewById(R.id.list) as ListView
    listView.adapter = ListExampleAdapter(this)
}

private class ListExampleAdapter(context: Context) : BaseAdapter() {
    internal var sList = arrayOf("Eins", "Zwei", "Drei")
    private  val mInflator: LayoutInflater

    init {
        this.mInflator = LayoutInflater.from(context)
    }

    override fun getCount(): Int {
        return sList.size
    }

    override fun getItem(position: Int): Any {
        return sList[position]
    }

    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? {
        val view: View?
        val vh: ListRowHolder

        if(convertView == null) {
            view = this.mInflator.inflate(R.layout.list_row, parent, false)
            vh = ListRowHolder(view)
            view.tag = vh
        } else {
            view = convertView
            vh = view.tag as ListRowHolder
        }

        vh.label.text = sList[position]
        return view
    }
}

private class ListRowHolder(row: View?) {
    public val label: TextView

    init {
        this.label = row?.findViewById(R.id.label) as TextView
    }
}
}

The layouts are exactly as here: https://github.com/bidrohi/KotlinListView/tree/master/app/src/main/res/layout

The full error message I'm getting is this: Error:(92, 31) Type inference failed: Not enough information to infer parameter T in fun findViewById(p0: Int): T! Please specify it explicitly.

I'd appreciate any help I can get.


Solution 1:

You must be using API level 26 (or above). This version has changed the signature of View.findViewById() - see here https://developer.android.com/about/versions/oreo/android-8.0-changes#fvbi-signature

So in your case, where the result of findViewById is ambiguous, you need to supply the type:

1/ Change

val listView = findViewById(R.id.list) as ListView to

val listView = findViewById<ListView>(R.id.list)

2/ Change

this.label = row?.findViewById(R.id.label) as TextView to

this.label = row?.findViewById<TextView>(R.id.label) as TextView

Note that in 2/ the cast is only required because row is nullable. If label was nullable too, or if you made row not nullable, it wouldn't be required.

Solution 2:

Andoid O change findViewById api from

public View findViewById(int id);

to

public final T findViewById(int id)

so, if you are target to API 26, you can change

val listView = findViewById(R.id.list) as ListView

to

val listView = findViewById(R.id.list)

or

val listView: ListView = findViewById(R.id.list)

Solution 3:

Its Working

API Level 25 or below use this

    var et_user_name = findViewById(R.id.et_user_name) as EditText

API Level 26 or Above use this

    val et_user_name: EditText = findViewById(R.id.et_user_name)

Happy Coding !

Solution 4:

Change your code to this. Where the main changes occurred are marked with asterisks.

package com.phenakit.tg.phenakit

import android.content.Context
import android.os.Bundle
import android.support.design.widget.BottomNavigationView
import android.support.v7.app.AppCompatActivity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ListView
import android.widget.TextView

public class MainActivity : AppCompatActivity() {

    private var mTextMessage: TextView? = null

    private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
        when (item.itemId) {
            R.id.navigation_home -> {
                mTextMessage!!.setText(R.string.title_home)
                return@OnNavigationItemSelectedListener true
            }
            R.id.navigation_dashboard -> {
                mTextMessage!!.setText(R.string.title_dashboard)
                return@OnNavigationItemSelectedListener true
            }
            R.id.navigation_notifications -> {
                setContentView(R.layout.activity_list_view)
                return@OnNavigationItemSelectedListener true
            }
        }
        false
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mTextMessage = findViewById(R.id.message) as TextView?
        val navigation = findViewById(R.id.navigation) as BottomNavigationView
        navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)


        **val listView = findViewById<ListView>(R.id.list)**



        **listView?.adapter = ListExampleAdapter(this)**
    }

    private class ListExampleAdapter(context: Context) : BaseAdapter() {
        internal var sList = arrayOf("Eins", "Zwei", "Drei")
        private  val mInflator: LayoutInflater

        init {
            this.mInflator = LayoutInflater.from(context)
        }

        override fun getCount(): Int {
            return sList.size
        }

        override fun getItem(position: Int): Any {
            return sList[position]
        }

        override fun getItemId(position: Int): Long {
            return position.toLong()
        }

        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? {
            val view: View?
            val vh: ListRowHolder

            if(convertView == null) {
                view = this.mInflator.inflate(R.layout.list_row, parent, false)
                vh = ListRowHolder(view)
                view.tag = vh
            } else {
                view = convertView
                vh = view.tag as ListRowHolder
            }

            vh.label.text = sList[position]
            return view
        }
    }

    private class ListRowHolder(row: View?) {
        public var label: TextView

        **init { this.label = row?.findViewById<TextView?>(R.id.label) as TextView }**
    }
}

Solution 5:

I would suggest you to use synthetics kotlin Android extension:

https://kotlinlang.org/docs/tutorials/android-plugin.html

https://antonioleiva.com/kotlin-android-extensions/

In your case the code will be something like this:

init {
   this.label = row.label
}

As simple as that ;)