How to Effectively Manage Data and Bind UI in Android Applications
Ultimate Guide to View Binding, Shared Preferences, and Data Sharing for Smooth Android Apps
View Binding in Android
View binding is a feature that makes it easier to interact with views.
generates a binding class for each XML layout present in the module,
activity_main.xml
toActivityMainBinding
instance of a binding class contains a direct reference to all views that have an id in the layout.
replaces the
findViewById
method.
Enabling View Binding :
//app level gradle file android
android {
...
buildFeatures {
viewBinding = true
}
}
If you want a layout file to be ignored while generating binding classes, add the tools:viewBindingIgnore="true"
attribute to the root view of that layout file
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>
Implementing View Binding :
private lateinit var binding: ResultProfileBinding
//set up instance of the Binding class
//ResultProfile is the module name
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ResultProfileBinding.inflate(layoutInflater)
//inflate() method creates an instance of the binding class for the activity to use.
val view = binding.root
//getting a reference to the root view
setContentView(view)
//Pass the root view to setContentView() to present it on screen
binding.name.text = "hey"
binding.button.setOnClickListener { .. }
//accessing views by BindingObject.viewID
}
Implementing View Binding in Fragments :
as the fragment’s view lifecycle is different from the activity lifecycle, hence we need to handle the binding objects lifecycle carefully to avoid memory leaks.
//import..
class ExampleFragment : Fragment() {
private var binding: FragmentExampleBinding? = null
//a Fragment's view lifecycle is shorter
//Declaring binding as nullable (FragmentExampleBinding?) allows us to
//safely set it to null in onDestroyView() when the view is destroyed.
private val bind get() = binding!!
//Non-nullable reference to the binding object,
//instead of writing binding!! every time
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
binding = FragmentExampleBinding.inflate(inflater, container, false)
//initialization of binding variable
return bind.root // Return the root view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Use binding to access views
bind.textView.text = "Hello, View Binding in Fragment!"
}
override fun onDestroyView() {
super.onDestroyView()
binding = null // Clear the binding object to avoid memory leaks
}
}
Shared Preferences & Data Saving in Android
Shared Preferences allows us to save & retrieve data in the form of key:value
pair. Used to save small amounts of data that should persist across app sessions, like user preferences, settings, etc.
Data belonging to primitive data types(integer, float, boolean, string, long)
Shared Preferences files are managed by an Android Framework and can be accessed anywhere within the application to read and write data
Implementation -
//MainActivity.kt
//import..
class MainActivity : AppCompatActivity() {
lateinit var userName : EditText
lateinit var userCount : Button
var name : String? = null
var count : Int? = null
var countNum = 0
lateinit var sharedPreferences : SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
//statements//
userName = findViewById(R.id.nameInput)
userCount = findViewById(R.id.countBtn)
userCount.setOnClickListener{
countNum++
userCount.setText(""+countNum)
}
}
override fun onPause() {
//onPause method is the best time to save the values
super.onPause()
sharedPreferences = this.getSharedPreferences("saveData", Context.MODE_PRIVATE)
//getSharedPrefereces used to get the instance of SharedPreference
//where saveData is the name of the file
//MODE_PRIVATE makes the created file only accessible by the calling application
name = userName.text.toString()
count = userCount.text.toString().toInt()
val editor : Editor = sharedPreferences.edit()
//Editor is used to edit the values in the SharedPreference
editor.putString("NAME:",name)
editor.putInt("COUNT:", count!!)
editor.apply()
//apply() saves the values
Toast.makeText(this,"DATA SAVED",Toast.LENGTH_SHORT).show()
}
override fun onResume() {
//onResume is the best time to retreive data
//as onCreate isn't called when acitivity regains focus
super.onResume()
sharedPreferences = this.getSharedPreferences("saveData", Context.MODE_PRIVATE)
name = sharedPreferences.getString("NAME:",null)
count = sharedPreferences.getInt("COUNT:",0)
userName.setText(name)
userCount.setText(""+count)
}
}
Sharing Data between Activities & Fragments
In this article, we will learn how to pass data between different mobile screens in Android.
Sharing Data b/w Activities :
Sharing Data with Intent used for small amounts of temporary data.
//SENDING DATA ->
intent.putExtra("name",username)
intent.putExtra("age",userage)
//parameter: (key, value)
//RECEIVING DATA ->
intent.getStringExtra(name)
intent.getIntExtra(age,0)
//parameter: (key, default value)
Sharing Data with Shared Preferences for simple, small data.
// Saving data in Activity A
val sharedPreferences = getSharedPreferences("MyPrefs", MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.putString("KEY_NAME", "John Doe")
editor.apply()
// Retrieving data in Activity B
val sharedPreferences = getSharedPreferences("MyPrefs", MODE_PRIVATE)
val name = sharedPreferences.getString("KEY_NAME", "default_value")
Sharing Data with a Singleton Class for large, complex data.
//SessionManager.kt
object SessionManager {
var userId: String? = null
var authToken: String? = null
fun isLoggedIn() = userId != null && authToken != null
fun clearSession() {
userId = null
authToken = null
}
}
//set session data : after login
SessionManager.userId = "12345"
SessionManager.authToken = "abcd1234"
//access session data
if (SessionManager.isLoggedIn()) {
println("User ID: ${SessionManager.userId}")
} else {
println("Not logged in")
}
ViewModel & LiveData provided by Jetpack Compose, persistent storage methods like Room Database(provided by Jetpack Library) or SQLite Database, using File Methods(JSON, text, etc) can also be used.
Persistent Storage Methods(Shared Preference, Files & Database) are used when data is needed across app restarts.
Sharing Data from Activity to Fragment :
Sharing Data with Bundle used for small amounts of data
//SENDING DATA ->
//needs to be executed before committing the fragment transaction
val fragment = ExampleFragment()
val bundle = Bundle() //creating an object of Bundle class
bundle.putInt("position",position) //passing the tag & data
fragment.arguments = bundle //binding the bundle to the fragment
//begin fragment transaction
//RECEIVING DATA ->
arguments?.getInt("position",0) //tag & default value is passed
Sharing Data with Interface Callback used for Fragment to Activity Communication
//Define Interface in Fragment
class ExampleFragment : Fragment() {
//Define an interface for the communication
interface OnDataPassListener {
fun onDataPass(data: String)
}
// Declare a variable to hold the listener reference
// listener -> activity that implements interface
private var dataPassListener: OnDataPassListener? = null
//Attach the listener when the fragment is attached to the activity
override fun onAttach(context: Context) {
super.onAttach(context)
dataPassListener = context as? OnDataPassListener
}
// Method to pass data to the activity
fun passDataToActivity(data: String) {
dataPassListener?.onDataPass(data) // Trigger the listener method
}
// Example of when you might call passDataToActivity()
// You could call this method when the user clicks a button, etc.
// Pass Data
fun someAction() {
val data = "Hello from Fragment!"
passDataToActivity(data) // Pass data to the activity
}
}
//Implement Interface in Activity
class MainActivity : AppCompatActivity(), ExampleFragment.OnDataPassListener {
//inherit from Interface
override fun onCreate(savedInstanceState: Bundle?) {
//statements..
// Add fragment dynamically
if (savedInstanceState == null) {
val fragment = ExampleFragment()
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit()
}
}
//Implement the onDataPass() method from the fragment's interface
override fun onDataPass(data: String) {
// Handle the data passed from the fragment
Toast.makeText(this, "Data from Fragment: $data", Toast.LENGTH_SHORT).show()
}
}
Sharing Data with Singleton used for shared data between multiple holders
object DataHolder {
var sharedData: String? = null
}
//Activity
DataHolder.sharedData = "Hello Fragment"
//Fragment
val data = DataHolder.sharedData
Shared ViewModel provided by Jetpack Compose, Third Party Libraries like EventBus or LiveDataBus can be used.
Sharing Data from Fragment to Activity :
Sharing Data with Activity Object i.e. directly accessing the Activity
from the Fragment
.
//SENDING DATA from Fragment
private var activityCallback: MainActivity? = null
override fun onAttach(context: Context) {
super.onAttach(context)
activityCallback = activity as? MainActivity
}
fun passDataToActivity() { //pass data to activity
activityCallback?.gettingData("Hello Activity!", 21)
}
//(activity as MainActivity).gettingData(userName,userAge)
//RECEIVING DATA -> MainActivity.kt
//user-defined function of our activity class
fun gettingData(userName : String, userAge : Int){
//used to receive data from fragment
val name : String = "Name: $userName"
val age : String = "Age: $userAge"
}
Sharing Data with Interface Callback used for Fragment & Activity Communication
class ExampleFragment : Fragment() {
// Define an interface for data communication
interface OnDataPass {
fun onDataPass(data: String)
}
var dataPasser: OnDataPass? = null
override fun onAttach(context: Context) {
super.onAttach(context)
// Ensure the activity implements the interface
dataPasser = context as? OnDataPass
}
fun passDataToActivity() {
// Pass data to the activity
dataPasser?.onDataPass("Hello Activity!")
}
}
class MainActivity : AppCompatActivity(), ExampleFragment.OnDataPass {
override fun onDataPass(data: String) {
println("Received data: $data")
}
}
Sharing Data b/w Fragments :
Sharing Data by using Activity as a Mediator which involves Fragment to Share Data to the Activity and then another Fragment to receive it from Activity. Helpful when switching layout to the ReceiverFragment with sharing the data.
class FragmentA : Fragment() { //Sender
var dataPasser: DataPassListener? = null
interface DataPassListener {
fun passData(data: String)
}
override fun onAttach(context: Context) {
super.onAttach(context)
dataPasser = activity as? DataPassListener
}
fun sendDataToFragmentB() {
dataPasser?.passData("Hello Fragment B")
}
}
class FragmentB : Fragment() { //Receiver
private var receivedData: String? = null
fun setReceivedData(data: String) {
receivedData = data
}
}
class MainActivity : AppCompatActivity(), FragmentA.DataPassListener {
override fun passData(data: String) {
val fragmentB = supportFragmentManager.findFragmentByTag("FragmentB") as? FragmentB
fragmentB?.setReceivedData(data)
}
}
Sharing Data by attaching Bundle during Fragment Call from another Fragment.
class FragmentA : Fragment() { //sender
fun passDataToFragmentB() {
val fragmentB = FragmentB()
val bundle = Bundle()
bundle.putString("key", "Hello Fragment B")
fragmentB.arguments = bundle
fragmentManager?.beginTransaction()?
.replace(R.id.fragment_container, fragmentB)?.commit()
}
}
class FragmentB : Fragment() { //receiver
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val data = arguments?.getString("key")
println("Received data: $data")
}
}
Use Singleton Method, if you don’t want the layout to switch while sharing the data.