How to Declare on UI Component in Android with Kotlin

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 Views 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



Leave a reply



Submit