What is the best way to declare on UI component in android with Kotlin?
This is a good use case for lateinit
. Marking a property lateinit
allows you to make it non nullable, but not assign it a value at the time that your Activity's constructor is called. It's there precisely for classes like Activities, when initialization happens in a separate initializer method, later than the constructor being run (in this case, onCreate
).
private lateinit var btnProceed: Button
If the property is read before a real value is assigned to it, it will throw an exception at runtime - by using lateinit
, you're taking the responsibility for initializing it before you access it for the first time.
Otherwise, if you want the compiler to guarantee safe access for you, you can make the Button
nullable as the converter does by default. Instead of the unsafe !!
operator though, which the converter often uses, you should use the safe call operator where you access the property:
btnProceed?.setOnClickListener { ... }
This will make a regular call if btnProceed
is a non-null value, and do nothing otherwise.
On a final note, you can check out Kotlin Android Extensions, which eliminates the need to create properties for your View
s altogether, if it works for your project.
Last edit (for now): you should also look at using lazy
as described in the other
answers
. Being lazy is cool.
How to initialize and use UI elements
From ViewBinding official docs:
View binding is a feature that allows you to more easily write code that interacts with views
First, enable ViewBinding in your module:
android {
...
buildFeatures {
viewBinding true
}
}
Then, if you're calling views from activity, you should:
private lateinit var binding: ResultProfileBinding
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
binding = ResultProfileBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
and then you use binding instance to call the views:
binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }
If you are calling views from a fragment, you should do it like following to avoid leaks:
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
Creating custom reusable view component android Kotlin
Try to create a custom layout for your buttons and style them as you need, let's call it custom_button_layout.xml
for example:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:orientation="vertical">
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
<!-- Apply your button style here -->
/>
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
<!-- Apply your button style here -->
/>
</LinearLayout>
In order to add custom text or properties in general to your button you should declare a custom style in your /res/values/attrs.xml
file (Create one if you don't have it in your project):
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomButtonLayout">
<attr name="button1Text" format="string"/>
<attr name="button2Text" format="string"/>
</declare-styleable>
</resources>
Then create a custom view class extending the LinearLayout
superclass and assign to it the layout just created.
Handle the click event on the two buttons with the setOnClickListener
method and retrieve your custom stylable attributes in the constructor:
class CustomButtonLayout (
context: Context,
attrs: AttributeSet
) : LinearLayout(context, attrs) {
init {
inflate(context, R.layout.example, this)
val customAttributesStyle = context.obtainStyledAttributes(attrs, R.styleable.CustomButtonLayout, 0, 0)
val button1 = findViewById<Button>(R.id.button1)
val button2 = findViewById<Button>(R.id.button2)
try {
button1.text = customAttributesStyle.getString(R.styleable.CustomButtonLayout_button1Text)
button2.text = customAttributesStyle.getString(R.styleable.CustomButtonLayout_button2Text)
} finally {
customAttributesStyle.recycle()
}
button1.setOnClickListener {
// Handle button1 click event...
}
button2.setOnClickListener {
// Handle button2 click event...
}
}
}
Finally, use your custom button in your XML layouts with its custom tag and attributes:
<CustomButtonLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:button1Text="Button 1 text"
app:button2Text="Button 2 text"/>
how to initialize a UI-component lazily
Instead of using var
you should use val
val mTextViewResult : TextView by lazy { findViewById(R.id.tvResult) }
DEPRECATED
Furthermore, if kotlin android extensions plugin is applied you do not have to call findViewById()
too.
In application level build.gradle
add plugin for kotlin android extension
apply plugin: "com.android.application"
apply plugin: "kotlin-android"
apply plugin: "kotlin-kapt"
apply plugin: "kotlin-android-extensions" // this plugin
...
Now you can use tvResult
by importing your layout reference.
import kotlinx.android.synthetic.main.<layout>.*
class MainActivity : AppCompatActivity{
...
}
Can't access EditText or other UI components with Kotlin
In the onCreateView just return the inflated view.
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater!!.inflate(R.layout.fragment_sign_in, container, false)
}
In the onViewCreated you can access your view components
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
userNameField.setText("hello world")
}
Populate UI from a ViewModel in Kotlin
You need to move all the logic to ProfileFragment once you navigate to ProfileFragment data will be set.
Example:
ProfileFragment
class ProfileFragment : Fragment() {
private val customerViewModel: CustomerViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_profile, container, false)
val name = view.findViewById<TextView>(R.id.name)
val surname = view.findViewById<TextView>(R.id.surname)
val email = view.findViewById<TextView>(R.id.email)
val contact = view.findViewById<TextView>(R.id.contact)
//calling initially here
customerViewModel.retrieveCustomer()
customerViewModel.liveData.observe(viewLifecycleOwner, Observer {
//customer index at 0
val customer = it[0]
name.text = customer.name
surname.text = customer.surname
email.text = customer.email
contact.text = customer.contactNo
})
return view
}
CustomerViewModel.kt
class CustomerViewModel(application: Application) : AndroidViewModel(application) {
var liveData = MutableLiveData<List<Customer>>()
fun retrieveCustomer(){
//Your logic to get data from Firebase or any remote or db
val listOfCustomer = mutableListOf<Customer>()
val customer = Customer("name", "surname","email", "contqct")
listOfCustomer.add(customer)
liveData.postValue(listOfCustomer)
}
}
fragment_profile.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Name"
android:padding="8dp"
android:textSize="24dp"
/>
<TextView
android:id="@+id/surname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Surname"
android:padding="8dp"
android:textSize="24dp"
/>
<TextView
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Name"
android:padding="8dp"
android:textSize="24dp"
/>
<TextView
android:id="@+id/contact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Name"
android:padding="8dp"
android:textSize="24dp"
/>
</LinearLayout>
Using Binding for accessing UI element of parent activity from fragment
Since your binding
is private to MainActivity
you can refer to your textView
from the MainActivity
only. To show/hide this view from FirstFragment
you can create a public function in MainActivity
and call it from your FirstFragment
.
class MainActivity: AppCompatActivity {
private var _binding: ActivityMainBinding? = null
private val binding get() = _binding!!
fun showHideTextView(visible: Boolean) {
binding.textView.isVisible = visible
}
}
And in your fragment, you can call:
(requireActivity() as MainActivity).showHideTextView(false) // This will hide the textView
Related Topics
Android and Getting a View with Id Cast as a String
PDF Library to Rendering the PDF Files in Android
How to Check If Class Exists Somewhere in Package
Android Runtime Permissions- How to Implement
Listview Viewholder Checkbox State
Why Can't I Use Resources.Getsystem() Without a Runtime Error
Convert a String to a Byte Array and Then Back to the Original String
Xmpp with Java Asmack Library Supporting X-Facebook-Platform
Google Gson Linkedtreemap Class Cast to Myclass
"Hello World" Android App with as Few Files as Possible, No Ide, and Text Editor Only
Convert Normal Java Array or Arraylist to JSON Array in Android
How to Draw an Arrowhead (In Android)
How to Close Another App in Android
Deadobjectexception on Android App
How to Move Firebase Child from One Node to Another in Android
How to Remove Specific Permission When Build Android App with Gradle