Android Studio: A real rookie problem with my button - android

i don't know why but i get this error:
e: C:\Users\User\AndroidStudioProjects\StorageManagementTheThird\app\src\main\java\com\example\storagemanagementthethird\AddItemActivity.kt: (15, 35): Type inference failed: Not enough information to infer parameter T in fun <T : View!> findViewById(p0: Int): T!
in this code:
class AddItemActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.add_item)
val addItemSubmitButton = findViewById(R.id.addItemSubmitButton)
addItemSubmitButton.setOnClickListener {
val editTextItemName: EditText = findViewById(R.id.editTextItemName)
//alles wat we aanpassen in storage en opslaan
val database = getSharedPreferences("database", Context.MODE_PRIVATE)
database.edit().apply {
putString("savedItemName", editTextItemName.text.toString())
}.apply()
}
}
}
please help me if you can :)

You should change this line
val addItemSubmitButton = findViewById(R.id.addItemSubmitButton)
like below
val addItemSubmitButton = findViewById<View>(R.id.addItemSubmitButton)

findViewById takes a type, so it can return the correct type of view - in this case you should probably use findViewById<Button> since that's what the view it meant to be, but it doesn't really matter since you can set a click listener on any type of View
If you actually specify the type on the val declaration, like val addItemSubmitButton: Button then the compiler can infer the type for the findViewById call, and you don't need the type in the diamond brackets (it'll be greyed out and you'll get a suggestion to delete it). That's why your editTextItemName lookup is fine - you're specifying the type for that one

Related

getParcelableArrayListExtra causes a different type to be set to a variable

The problem starts with getParcelableArrayListExtra doesn't support type check when we try to set it to a variable. Let me give an example as basic as I can.
A User Class.
import kotlinx.parcelize.Parcelize
import android.os.Parcelable
#Parcelize
data class UserClass(
var name: String? = null,
var text: String? = null,
var age: Int? = null
) : Parcelable
The random class which we'll try to set to the User variable.
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
#Parcelize
data class MessageClass(
val title: String?, = Constant.STRING_EMPTY
val text: String? = Constant.STRING_EMPTY
) : Parcelable
The class that fills intent
class FillIntentClass(){
//Let's say one of the developers added the MessageClass object inside our intent.
//Or BE sent the wrong type of object and I passed its value to the intent.
private fun DummyFunctionToSetIntent(){
val messageList = arraylistOf(MessageClass(title = "hello",text ="dummy text")
intent.putParcelableArrayListExtra(EXTRA_PAYMENT_OPTIONS_EXTRA, messageList)
}
}
Test class
class MyTestClass(){
// UserList variable
private var mUserList: ArrayList<UserClass>? = null
override fun onCreate(savedInstanceState: Bundle?) {
...
with(intent) {
// In this situation, mUserList became the type of ArrayList<MessageClass>
// But it shouldn't be possible. Because it must accept only ArrayList<UserClass>
// And that causes mostly crashes when the other code parts use it.
mUserList = getParcelableArrayListExtra(EXTRA_PAYMENT_OPTIONS_EXTRA)
// mUserList now pretend its like ArrayList<MessageClass>. But i set it as ArrayList<UserClass> at the top of the class.
// The best way to solve this is to type check with as?. If the type is not as expected it must return null.
// But I cannot use type check here. It gives me a "Not enough information to infer type variable T" error.
mUserList = getParcelableArrayListExtra(EXTRA_PAYMENT_OPTIONS_EXTRA) as? ArrayList<UserClass> //(compile error here on IDE)
// So I had to come out with the below solution. But I cannot say it's the best practice.
if (getParcelableArrayListExtra<UserClass>(EXTRA_PAYMENT_OPTIONS_EXTRA)
?.filterIsInstance<UserClass>()?.isNotEmpty() == true
) {
mUserList = getParcelableArrayListExtra(EXTRA_PAYMENT_OPTIONS_EXTRA)
}
}
}
}
Type check(as,as?) works with getParcelable functions as expected. But when it comes to the getParcelableArrayListExtra it just doesn't work and gives compile error as I explained above.
Do you have any knowledge of what's the best option for as, as? check? And how it's possible for mUserList to accept a different type of Array and pretend like it?
This is a mess for a few reasons:
You are coding in Kotlin, but the classes you are dealing with (Parcelable, Bundle, Intent, ArrayList) are actually Java
Generics in Java are a hack
I would split the problem into 2 parts:
Unparcel the ArrayList into ArrayList<Parcelable>
Check/convert the contents of the ArrayList<Parcelable> into the expected type
Check the API level and code accordingly:
if (Build.VERSION.SDK_INT >= 33) {
data = intent.getParcelableExtra (String name, Class<T> clazz)
}else{
data = intent.getParcelableExtra("data")
}
Also you can use these extensions for bundle and intent:
inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? = when {
SDK_INT >= 33 -> getParcelableExtra(key, T::class.java)
else -> #Suppress("DEPRECATION") getParcelableExtra(key) as? T
}
inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? = when {
SDK_INT >= 33 -> getParcelable(key, T::class.java)
else -> #Suppress("DEPRECATION") getParcelable(key) as? T
}

Kotlin Change ViewText with an ID provided by a String

Goal: To get a ViewText resource and edit it from an activity, using a mutable string (because then the string can be changed to alter other ViewTexts in the same function).
Context: I'm making a grid using TableRows and TextViews that can be altered to form a sort of map that can be generated from an array.
Issue: The binding command does not recognise strings. See my comment "PROBLEM HERE".
Tried: getResources.getIdentifier but I've been told that reduces performance drastically.
An excerpt from gridmap.xml
<TextView
android:id="#+id/cell1"/>
GridMap.kt
package com.example.arandomadventure
import android.R
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.arandomadventure.databinding.GridmapBinding
class GridMap : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//sets the binding and assigns it to view
val binding: GridmapBinding = GridmapBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
//creates a string variable
var cellID = "cell1"
//uses binding to set the background colour to teal
binding.cellID.setBackgroundResource(R.color.teal_200) //<- PROBLEM HERE (Unresolved Reference)
//getResources.getIdentifier is not an option as it degrades performance on a larger scale
}
}
A binding object is just an autogenerated class, whose class members are defined by the views in your layout XML. You can't add or access a field on a class with the syntax you showed - binding classes are no different from any other class. If you wanted to be able to access them by name, you could load them into a map
val viewMap = mapOf(
"cell1" to binding.cell1,
"cell2" to binding.cell2,
"cell3" to binding.cell3
)
then you can use the map to access them by name
var cellID = "cell1"
viewMap[cellID].setBackgroundResource(R.color.teal_200)
If you want the map to be a class member, you can set it like this
private lateinit var viewMap: Map<String,View>
override fun onCreate(savedInstanceState: Bundle?) {
//...
viewMap = mapOf(
"cell1" to binding.cell1,
"cell2" to binding.cell2,
"cell3" to binding.cell3
)
}
If your layout has hundreds of views and this becomes cumbersome, you may want to consider adding the views programmatically instead.
Edit
If you want to do this a more ugly, but more automatic way you can use reflection. To do this you need to add this gradle dependency:
implementation "org.jetbrains.kotlin:kotlin-reflect:1.7.0"
then you can build up the map programmatically with all views in the binding.
val viewMap = mutableMapOf<String,View>()
GridmapBinding::class.members.forEach {
try {
val view = it.call(binding) as? View
view?.let { v ->
viewMap[it.name] = v
}
}
catch(e: Exception) {
// skip things that can't be called
}
}
Or you can use this to call a method (keep in mind this will throw if no such class member exists):
var cellID = "cell1"
val view = GridmapBinding::class.members.filter { it.name == cellID }[0].call(binding)

How to reduce a healthlevel by 1 when a button is clicked in kotlin?

I'm starting out with kotlin. and I'm having trouble understanding OnClickListener. Here After setting the initial health level to 10, I need to reduce the health level by 1 and display it. So far I have initiliased the healthlevel and set the onclick, but How do declare the function to reduce it by 1 and call it when the button is clicked?
val TAG = "MyMessage"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.i("LIFECYCLE", "OnCreate")
}
private var healthLevel: Int = 10 //Set the initial health level to 10
private lateinit var healthLevelTextView: TextView
private lateinit var sneezeBtn: Button
private lateinit var takeMedicationButton: Button
private lateinit var blowNoseButton: Button
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("Answer", healthLevel)
Log.i(TAG, "Called SaveInstanceState()")
}
sneezeBtn.setOnClickListener{ _ ->
// the function goes here
}
Vitor has the answer, but just as a couple of alternatives...
You might want to create a function to update and display your value together:
fun setHealth(health: Int) {
healthLevel = health
healthLevelTextView.text = healthLevel
}
That couples the update with the display change, so the two things always happen together. And if you always set healthLevel with this function (instead of setting the variable directly), the display and value will always be in sync
override fun onCreate(savedInstanceState: Bundle?) {
// set your view variables, so the TextView is ready to update
setHealth(INITIAL_HEALTH_VALUE)
And if you like, Kotlin lets us put a setter function on the variable itself
var healthLevel: Int = 10
set(value) {
field = value
healthLevelTextView.text = health
}
so every time you change the value of healthLevel, it also updates the display. You can use the observable delegate too
var healthLevel: Int by Delegates.observable(10) { _, oldValue, newValue ->
healthLevelTextView.text = newValue
}
These are more advanced than just setting the value and updating the view yourself, just pointing out that they exist as alternatives and ways to keep your logic in one place. Also with these examples, they only run the code after you first set a value - they have a default of 10 in both cases, but initialising that default won't run the setter code or the observable function. So you'd still need to go healthLevel = 10 to get the text to display the initial value.
You can change your healthLevel variable by using:
sneezeBtn.setOnClickListener {
healthLevel--
healthLevelTextView.setText(healthLevel.toString())
}
As a side note, if you use lateinit you lose one of the best features of Kotlin, which is Null Safety. If you don't know how to use that yet, I recommend you to start learning it as soon as possible.
You can also use a very nice feature of Android with kotlin, where the objects for your views are generated automatically in your Activity, you just have to type your XML views id, instead of having to use findViewById everywhere.
I assume, your code sample is within an Activity class and you have a layout file activity_main.xml which looks something like this:
...
<TextView
android:id="#+id/healthLevelTextViewId"
...
/>
<Button
android:id="#+id/sneezeBtnId"
...
/>
...
This would be the code with your desired functionality:
class YourActivity : Activity {
private var healthLevel: Int = 10
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// retrieve references to the text view and button
val healthLevelTextView = findViewById<TextView>(R.id.healthLevelTextViewId)
val sneezeBtn = findViewById<Button>(R.id.sneezeBtnId)
// the OnClickListener must be initialized within a method body, as it is not a method itself.
sneezeBtn.setOnClickListener { _ ->
// implementation from the answer from Vitor Ramos
healthLevel--
healthLevelTextView.setText(healthLevel.toString())
}
}
}
You can further improve the code:
Add apply plugin: 'kotlin-android-extensions' to your app's build.gradle file. Then you can reference your views directly using their IDs. So instead of
val sneezeBtn = findViewById<Button>(R.id.sneezeBtnId)
sneezeBtn.setOnClickListener { ... }
you can use the Id directly like this:
sneezeBtnId.setOnClickListener { ... }
AndroidStudio will give you a hint to add an import for sneezeBtnId.
Use View Models, LiveData and Data Binding. This is the recommended, but a little bit more advanced technique.

Android Studio & Kotlin: unresolved reference:context

I've tried hundreds of ways to resolve this reference problem:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
val dm = DataManager()
val adapterCourses = ArrayAdapter<CourseInfo>(context: this,
android.R.layout)
but in ArrayAdapter<CourseInfo>(context: this, android.R.layout) I get unresolved reference: context and I have no idea why.
Android Studio version: 3.3.2
Kotlin version: 1.3.21
Could anyone help me?
I had a similar error message because I didn't import the Context.
If you haven't explicitly imported Context, try adding this to your import list near the start of your Activity file:
import android.content.Context
The column in Kotlin is used for some things, but not when passing named arguments. The syntax for passing a named parameter is parameterName = parameterValue.
When you write context = this, while passing a parameter, you are simply referring to the parameter context of the function you are calling, explicitly saying that this has to correspond to that context parameter. This is not very useful in this case unless you want to be very explicit.
The usefulness of using named arguments arise when you are dealing with optional parameters or when you are passing the parameters out of order.
E.g.
// DECLARATION of function abc
fun abc(s: String = "", i: Int = 0)
// USAGE of function abc passing only an Int
abc(i = 314)
The function abc has two parameters and they have a default value. In this case, you can avoid passing any parameter if you are fine with the defaults.
But if you only want to pass i, you can do it by specifying its name, as done in the example.
Similarly, you can choose to pass parameters out of order, in that case, you'll do:
abc(i = 314, s = "something")

Kotlin Property: "Type parameter of a property must be used in its receiver type"

I have the following simple Kotlin extension functions:
// Get the views of ViewGroup
inline val ViewGroup.views: List<View>
get() = (0..childCount - 1).map { getChildAt(it) }
// Get the views of ViewGroup of given type
inline fun <reified T : View> ViewGroup.getViewsOfType() : List<T> {
return this.views.filterIsInstance<T>()
}
This code compiles and works fine. But, I want the function getViewsOfType to be a property, just like the views. Android Studio even suggests it. I let AS do the refactoring and it generates this code:
inline val <reified T : View> ViewGroup.viewsOfType: List<T>
get() = this.views.filterIsInstance<T>()
But this code doesn't compile. It causes error: "Type parameter of a property must be used in its receiver type"
What is the issue here? Searching for help on this error doesn't seem to lead to an answer.
The error means that you can only have a generic type parameter for an extension property if you're using said type in the receiver type - the type that you're extending.
For example, you could have an extension that extends T:
val <T: View> T.propName: Unit
get() = Unit
Or one that extends a type that uses T as a parameter:
val <T: View> List<T>.propName: Unit
get() = Unit
As for why this is, I think the reason is that a property can't have a generic type parameter like a function can. While we can call a function with a generic type parameter...
val buttons = viewGroup.getViewsOfType<Button>()
... I don't believe a similar syntax exists for properties:
val buttons = viewGroup.viewsOfType<Button> // ??

Categories

Resources