I want to do Kotlin in Android but I have some issues with mutables. Let's say I'm doing a TextView that's accessed across multiple methods.
var tv: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
tv = find(R.id.tv)
}
fun clearText(){
tv?.setText("")
}
fun setText(text: String) {
tv?.setText(text)
}
This works, but having tv? every time I use the TextView is a code smell. Is there a better way of doing this? I feel like I should be using val instead of var but I can't find a way to put it in.
There's two ways I know of to solve this. Lateinit and Kotlin's Android extensions.
Lateinit lets you assign a value to a non-null type later in the classes lifespan.
lateinit var textView: TextView
override fun onCreate(savedInstanceState: Bundle?){
textView = findViewById(R.id.view_text)
}
You can now access it without a null check.
Or, you can use Kotlin's extensions.
Add the plugin to your build.gradle
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
And import the generated accessors into your Activity, Fragment or whatever.
import kotlinx.android.synthetic.main.layout_name.*
text_view_id.text = "Hello World!"
Related
I see many articles handling two way binding with MVVM. But i want to handle this issue with single source of truth. I dont want to create multiple stateflows for this case. I have a StateFlow which has all view state inside entity. How can i provide to sync with multiple EditText values to the StateFlow ?
I have added multiple TextWatcher's to sending event to ViewModel but this way causes so boilerplate code.
I got your question and I think that you need to use viewBinding feature,
so the first step you need to enable viewBinding features in your gradle (module).
android{
buildFeatures {
viewBinding = true //this is mandatory
}
...
}
then you can bind your view
I use my code as example
inside the activity class
you can declare viewBinding of your activity using lateinit and inflate your activity inside onCreate fun
class HomeActivity : AppCompatActivity() {
private lateinit var binding: ActivityHomeBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
setContentView(binding.root)
...
}
}
and finally you can easily access your view using binding val
example
binding.textView.text = "example"
the code above will be similar to
findViewById<TextView>(R.id.text_view).text ="example"
last night I noticed I'm not able to change the attributes of elements in the layout from my main activity
so I built a new project and I had the same problem there too.
I could not find out what was wrong with my android studio so I'd appreciate it if someone with the same problem helps me out.
as you see in the picture when I call a defined view from the layout in my activity its not recognized
the error will be: Unresolved reference: txtHello
Kotlin Synthetic imports not working ?
Well, there's always the age-old alternative:
val foo: TextView = findViewById(R.id.your_id)
I believe synthetics have been deprecated and I guess support for it has just now been completely removed
Alternatively, you can make use of ViewBinding, which is another alternative.
Enable it in your build.gradle:
android {
...
buildFeatures {
viewBinding true
}
}
This generates a Binding object for your layout, so you can make use of it like this:
private lateinit var binding: YourLayoutNameBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = YourLayoutNameBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
then you have access to all views on the layout, through the binding:
binding.name.text = "foo"
An alternative you can look at is ViewBinding, a concept in Android that was introduced recently.
You should take a look for this
https://developer.android.com/topic/libraries/view-binding
You cannot set view id directly for your use in app, instead you need findViewById(R.id.idTextHello).setOnClickListener()
That's how views are bind in application.
import kotlinx.android.synthetic.main.activity_main.*
but dont forget to apply kotlin extension
You are trying to access your views via Kotlin Synthetics, which have been deprecated.
You can use ViewBinding instead.
Enable it in your module level build.gradle:
android {
...
buildFeatures {
viewBinding true
}
}
And then in your activity you can access views like :
private lateinit var _binding: ActivityNameBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityNameBinding(layoutInflater)
val view = binding.root
setContentView(view)
}
and then you can access your views like this:
_binding.btn_start.setOnClickListener {
...
}
For detailed understanding of ViewBinding, you can look into this article:
https://medium.com/geekculture/android-viewbinding-over-findviewbyid-389401b41706
In my code I make use of the following Views in XML:
val googleButton: Button = findViewById<View>(R.id.google_login) as Button
val loginWithEmailText: TextView = findViewById(R.id.login_with_email_text)
val emailLoginButton: Button = findViewById(R.id.email_login_button)
val createAccountButton: Button = findViewById(R.id.email_create_account_button)
This code is extracted from a function inside my Kotlin class. Whenever I have to access these views, I need to write this code all over again.
Is there any way that I can access them from only one place in my class code? I tried putting them outside but the app won't start.
Thank you
You need to define these fields as a part of your class and initialize them once you set the layout resource for your Activity/Fragment. If you put these lines 1:1 in the class body, the initialization will fail, since the layout has not been inflated yet.
Please get familiar with the concept of lifecycle, so that you can understand how to approach View related topics: https://developer.android.com/guide/components/activities/activity-lifecycle
Please check out this snippet for a sample code:
class MyActivity: Activity() {
lateinit var textView: TextView
lateinit var button: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
// initialize your views here
textView = findViewById(R.id.text_view_id)
button = findViewById(R.id.button_id)
}
fun someOtherFunction(){
// you can reference your views here like normal properties
button.setOnClickListener { v -> callAnotherFunction() }
// ...
}
}
Since you are on Android, you might be interested in using Kotlin synthetic properties for referencing views without the whole boilerplate of finding them: https://antonioleiva.com/kotlin-android-extensions/. It's no longer a recommended practice to make use of it, but it's handy in some cases anyway.
I have lateinit property in my Kotlin activity, this is simplified version of that:
class CreateNewListOfQuestions : AppCompatActivity() {
lateinit var questionAnswerListOfObjects: ArrayList<QuestionAnswerObject>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_create_new_list_of_questions)
save.setOnClickListener {
questionAnswerListOfObjects.add(0, QuestionAnswerObject("question", "answer", 1))
}
}
}
The main problem is that when I generate mobile app and press “Save” button app stops working.
Logcat shows this error: “lateinit property questionAnswerListOfObjects has not been initialized”
I was trying many ways to initialize it but these ways did not help to me. How should I properly initialize it?
I am plining to add to ArrayList many objects of this class:
class QuestionAnswerObject(var question: String, var answer: String, var probability: Int=100) {}
It depends what you want.
For example if everything you need is using ArrayList<QuestionAnswerObject> you dont need lateinit at all:
var questionAnswerListOfObjects = ArrayList<QuestionAnswerObject>()
would be enough
If you want to get from Bundle or from something else - you must initialize it before using.
Basically lateinit keyword is used only to say "hey, compiler, I have a variable, it is not initialized yet, but I promise it will be initialized before I use it, so please mark it as not nullable field".
So if you really want to use lateinit, just init that property earlier, for example add after setContentView
questionAnswerListOfObjects = ArrayList<QuestionAnswerObject>()
Usually we do not use findViewById (R.id.listView) in kotlin because Android studio do it for us automatically (we do not need to find view).
But this example shows that we need to you it (in this line of code):
val listView = findViewById<ListView>(R.id.listView) as ListView.
Why do we use this line in this example? How to not use it?
If you're using findViewById from Kotlin, you should never need a cast (from API level 26 and up). You should use it one of these two ways:
val myTV1 = findViewById<TextView>(R.id.myTextView)
val myTV2: TextView = findViewById(R.id.myTextView)
And then you can access its properties via these variables:
myTV1.text = "testing"
This is a perfectly valid way of getting View references and using them in Kotlin as it is.
However, if you also have Kotlin Android Extensions enabled in the project (by the apply plugin: 'kotlin-android-extensions' line in your module level build.gradle file), you also can refer to your Views by their IDs via the synthetic properties it provides, just make sure you have the correct imports, for example:
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myTextView.text = "testing"
}
}
Note that Kotlin Android Extensions is entirely optional to use, and if you do use it, findViewById of course is still available if for whatever reason you want to mix the two methods.
In general, when you need a view from a layout file, you can import the following:
kotlinx.android.synthetic.main.<layout filename>.<id of view>
If you need all of the views from a layout file, you can use:
kotlinx.android.synthetic.main.<layout filename>.*
It's only applicable for Kotlin
Step 1:
Add this Kotlin Android Extensions in our code
Gradle Scripts -> build.gradle(Module)
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
NOTE: Now you can directly access the view id without using findViewbyId, in case if you face any red line press alt+enter, or import this in KT file.
import kotlinx.android.synthetic.main.activity_your_layout.*