Posts Tagged - android

Android WorkManager

WorkManager is an Android background tasks management system which came with Jetpack. We use it to run postponed background work. It will run even if the app is closed.

WorkManager is not for tasks that need to be run in a background thread, but they don’t need to survive the process death. Use Kotlin’s coroutines instead. WorkManager is for periodic work requests or one time work requests that need to run even if the user closses the App or needs constraints such as make sure the device is charging or be sure it uses WIFI.
It’s possible to chain multiple Workers together to run one after another.

Implementation

Add this to your build.gradle file.

dependencies {
        def work_version = "2.3.4"
        implementation "androidx.work:work-runtime-ktx:$work_version"
}

One Time Work Requests

Create a worker class. This will be executed by the work manager instance when conditions are met.

class UploadWorker(context: Context, params: WorkerParameters): Worker(context, prams) {
        override fun doWork(): Result {
                try {
                        // add your work here
                        return Result.success()
                } catch(e: Exception) {
                        return result.failure()
                }
        }
}

Now set it up.

class MainActivity: AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                setContentView(R.layout.activity_main)
                setOneTimeWorkRequest()
        }
        
        private fun setOneTimeWorkRequest() {
                val uploadRequest = OneTimeWorkRequest.Builder(UploadWorker::class.java)
                        .build()
                WorkManager.getInstance(applicationContext)
                        .enqueue(uploadRequest)
        }
}

Get status updates

For each work request, WorkManager allows us to get a LiveData<WorkInfo>. By observing it, we can determine the current status.

class MainActivity: AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                setContentView(R.layout.activity_main)
                setOneTimeWorkRequest()
        }
        
        // change this
        private fun setOneTimeWorkRequest() {
                val workManager = WorkManager.getInstance(applicationContext)
                val uploadRequest = OneTimeWorkRequest.Builder(UploadWorker::class.java)
                        .build()
                workManager.enqueue(uploadRequest)
                workManager.getWorkInfoByIdLiveData(uploadRequest.id)
                        .observe(this, Observer {
                                // this is just a TextView to show it off
                                textView.text = it.state.name
                        })
        }
}

Set up constraints

Sometimes we want to set up constraints. For example, if we want to download a large video, this consumes too much battery and we want to do it only if the battery’s connected.

class MainActivity: AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                setContentView(R.layout.activity_main)
                setOneTimeWorkRequest()
        }
        
        private fun setOneTimeWorkRequest() {
                val workManager = WorkManager.getInstance(applicationContext)
                // added this
                val constraints = Constraints.Builder()
                        .setRequiresCharging(true)
                        .setRequiredNetworkType(NetworkType.CONNECTED)
                        .build()
                
                val uploadRequest = OneTimeWorkRequest.Builder(UploadWorker::class.java)
                        // added this
                        .setConstraints(constraints)
                        .build()
                workManager.enqueue(uploadRequest)
                workManager.getWorkInfoByIdLiveData(uploadRequest.id)
                        .observe(this, Observer {
                                // this is just a TextView to show it off
                                textView.text = it.state.name
                        })
        }
}

Pass data to workers

Sometimes we need to pass data to the workers. It’s also possible to send data from the Workers to the Activity.

class MainActivity: AppCompatActivity() {

        // added this
        companion object {
                const val KEY_COUNT_VALUE = "key_count"
        }

        override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                setContentView(R.layout.activity_main)
                setOneTimeWorkRequest()
        }
        
        private fun setOneTimeWorkRequest() {
                val workManager = WorkManager.getInstance(applicationContext)

                // added this
                val data = Data.Builder()
                        .putInt(KEY_COUNT_VALUE, 125)
                        .build()
                
                val constraints = Constraints.Builder()
                        .setRequiresCharging(true)
                        .setRequiredNetworkType(NetworkType.CONNECTED)
                        .build()
                
                val uploadRequest = OneTimeWorkRequest.Builder(UploadWorker::class.java)
                        .setConstraints(constraints)
                        // added this
                        .setInputData(data)
                        .build()
                workManager.enqueue(uploadRequest)
                workManager.getWorkInfoByIdLiveData(uploadRequest.id)
                        .observe(this, Observer {
                                // this is just a TextView to show it off
                                textView.text = it.state.name
                        })
        }
}

We get it like this at our Worker.

class UploadWorker(context: Context, params: WorkerParameters): Worker(context, prams) {
        override fun doWork(): Result {
                try {
                        // 0 is the default value
                        val count = inputData.getInt(MainActivity.KEY_COUNT_VALUE, 0)
                        // add your work here
                        return Result.success()
                } catch(e: Exception) {
                        return result.failure()
                }
        }
}

Periodic work requests

For now, Android Jetpack has a minimum repeat time of 15 minutes. The only difference is we need use PeriodicWorkRequest class instead.

fun setPeriodicWorkRequest() {
        val periodicWorkRequest = PeriodicWorkRequest
                .Builder(DownloadingWorker::class.java, 16, TimeUnit.MINUTES)
                .build()
        WorkManager.getInstance(applicationContext).enqueue(periodicWorkRequest)
}

Read More

Android Hilt (dependency injection)

Hilt is an Android Jetpack wrapper library to make dependency injection with Dagger easier and more efficient.

We have the following third-party dependency. With Dagger2 we would need to create a @Module, inject it into a @Component and create code into main to .built() it.

class DataSource {
	fun getRemoteData() {
		...
	}
}

Still, all classes that are going to be injected need to be marked as follows with @Inject and an empty constructor().

class DataSource @Inject constructor()

All activities and their fragments need to be marked with @AndroidEntryPoint.

Implementation

Add this into your project’s root build.gradle file.

buildscript {
	dependencies {
		classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
	}
}

Then add this into your app’s build.gradle file.

apply plugin: 'dagger.hilt.android.plugin'

// check this is correctly set.
android {
	compileOptions {
		// since Hilt uses Java8 features, add this
		sourceCompatibility JavaVersion.VERSION_1_8
		targetCompatibility JavaVersion.VERSION_1_8
	}
}

dependencies {
	implementation "com.google.dagger:hilt-android:2.28-alpha"
	kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}

Then we need to annotate the our Application class.

@HiltAndroidApp
class App: Application()

Now go to your AndroidManifest.xml and set this attribute android:name\=".App".

<manifest>
	<application
	 	...
		android:name\=".App">
	</application>
</manifest>

With Hilt we don’t need to explicitly create a @Component interface. It already has 7 such interfaces packed for us.

Delete the @Component class and use the following annotations instead.

Hilt Component Injector for
ApplicationComponent Application
ActivityRetainedComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent View annotated with @WithFragmentBindings
ServiceComponent Service

When we use Dagger, we list all the modules belong to a Component, at the top of the Component. Now we include the component of a module at the top of the module instead.

@Module
@InstallIn(ApplicationComponent::class)
class DataModule {
	@Provides
	fun providesDataSource(): DataSource{
		return DataSource()
	}
}

Let’s switch to MainActivity. Remove the .inject part and add the new @AndroidEntryPoint annotation. This generates an individual Hilt component for each class marked with it.

@AndroidEntryPoint
class MainActivity: AppCompatActivity() {
	@Inject
	lateinit var dataSource: DataSource
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)
		
		// we may now remove this
		// (application as App).dataComponent.inject(this)
		
		dataSource.getRemoteData()
	}
}

Remember to rebuild the project.

Read More

Android Unit Testing

When we test in Android, we test our ViewModel. They don’t contain any View code. This is great for testing and this way we don’t depend on Android.

Implementation

We ought to mock threads. If we don’t, we’d have to run the entire Android system and this is something we want to avoid.

Add the following to your build.gradle file. All testImplementation and androidTestImplementation won’t be included in your APK.

def arch_version = "2.1.0"

testImplementation 'junit:junit:4.13'
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.2.1"
androidTestImplementation 'androidx.test.ext:junit:1.1.2'

// room testing
androidTestImplementation "androidx.arch.core:core-testing:2.1.0"

// mockito
testImplementation "org.mockito:mockito-core:1.10.19"
	
// user interface testing
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

// test LiveData
testImplementation "androidx.arch.core:core-testing:$arch_version"

// more readable assertions
testImplementation "com.google.truth:truth:1.0.1"

Read More

Dagger2 (dependency injection)

Dagger2 is a library that allows us to achieve dependency injection. It has three main concepts.

  • @Inject tells dagger which variables to inject.
  • @Module defines how to create the objects that we want to inject.
  • @Component links the two together.

When we make any changes to the data code, we need to rebuild the project and the system will generate for us the classes that we need in order to perform the injections.

Implementation

Go into app.build and add the following

apply plugin: 'kotlin-kapt'

def daggerVersion = '2.13'

dependencies {
	implementation "com.google.dagger:dagger:$daggerVersion"
	implementation "com.google.dagger:dagger-android-support:$daggerVersion
	// kotlin annotation processor
	kapt "com.google.dagger:dagger-compiler:$daggerVersion"
	kapt "com.google.dagger:dagger-android-processor:$daggerVersion"
}

Read More

Android Notifications

Most notification features need at least API 26 (Android 8) to work.

Implementation

We have to first create a NotificationChannel. Then we define a NotificationManager instance.

A NotificationChannel has an ID, a name and a description. It also has an importance level. This defines how to interrupt the user for any notification belonging to this channel.

We need to create the NotificationChannel before posting any notification.

class MainActivity: AppCompatActivity() {
	private val channelID = "es.codes.mario.template.channel1"
	private var notificationManager: NotificationManager? = null
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)
		
		notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
		createNotificationChannel(channelID, "TemplateChannel", "oh no! this is the channel's description")
		
		button.setOnClickListener {
			displayNotification()
		}
	}
	
	private fun displayNotification() {
		val notificationId = 45
		val notification = NotificationCompat.Builder(this@MainActivity.channelID)
			.setContentTitle("Template title")
			.setContentText("This is a test notification")
			.setSmallIcon(android.R.drawable.ic_dialog_info)
			.setAutoCancel(true)
			.setPriority(NotificationCompat.PRIORITY_HIGH)
			.build()
			
		notificationManager?.notify(notificationId, notification)
	}
	
	private fun createNotificationChannel(id: String, name: String, description: String) {
		// has to be higher than Oreo
		if(Build.VERSION.SDK_INT >= Build.VERSION_CODES_O) {
			val importance = NotificationManager.IMPORTANCE_HIGH
			val channel = NotificationChannel(id, name, importance).apply {
				description = channelDescription		
			}
		}
		
		notificationManager?.createNotificationChannel(channel)
		}
	}
}

Read More

Android SQLite Experience

Before reading this post, check Room SQLite Framework for Android

I’ve had a lot of trouble to connect to my Android Studio emulator SQLite database to run queries. Stetho didn’t show my databases and amitshekhar just plain didn’t work for me.

The solution was to externally use ADB to connect to a rooted, non-google-play emulator, run a set of queries there and directly work with SQLite through Command Line Interface.

Troubles to connect to SQLite instance

ADB not found in system

ADB is installed with Android Studio. For windows, add the following link to your system environment variables.

C:\Users\{your_user}\AppData\Local\Android\Sdk\platform-tools

Log-out and log-in for it to take place. Then you should be able to open your CMD and run adb queries there.

adbd cannot run as root in production builds

This happens when you try to get sudo permissions in a Google Play emulator image. Just get into Android Studio AVD manager, create a new emulator and use an image which doesn’t have the Google Play icon.

ADB remount doesn’t work

One of the proposed solutions was to run this set of queries.

adb root
// this command failed
adb remount
sqlite3 data/data/your/app/path/database/your_database

which failed spectacularly.

avb_ops_user.cpp:217: ERROR: Error writing data.
avb_user_verity.c:205: ERROR: Error writing to partition 'vbmeta'
Skipping /system for remount
...
Operation not permitted
Can not mount overlayfs for partitions: No such file or directory
failed to remount partition dev:/dev/block/dm-3 mnt:/vendor: Read-only file system

This went nowhere. See solution below.

Connect to the emulator

Open CMD and run this set of queries to open a database connection.

adb shell
sqlite3 data/data/es.codes.mario.weighttracker/databases/entry_database

You should have an open connection now.
If this doesn’t work, try to run the following commands.

adb shell
su
mount -o rw,remount /system
sqlite3 data/data/es.codes.mario.weighttracker/databases/entry_database

SQLite Queries

I’m used to work with MySQL. This are queries which change for SQLite and are not guaranteed to be standard SQL.

.tables # list all tables
.schema tablename # describes the table

Read More

Kotlin Coroutines

Introduction

There are two types of multitasking methods to manage multiple concurrent processes. In one type the OS controls the swich bewteen processes. The other type is called cooperative multitasking, in which processes control their behaviour by themselves.

Coroutines are software components that create subroutines for coporative multitasking. A coroutine can be introduced as a sequence of well managed sub tasks. To some extend, a coroutine can be considered as a light weight thread. You can execute many coroutines in a single thread. A coroutine can also be swiched between threads, and can be suspended from one thread and resumed from another thread.

In kotlin there’s a coroutine library since 1.3, which avoids us the need for RxJava, AsyncTask, Executors, HandlerThreads and IntentServices. The coroutines API allows us to write asynchronous code in a sequential manner.

Importance of coroutines

For a phone with a 90hz screen, our app has only 11ms (1000 ms / 90 refresh per second) to perform tasks in the Android main Thread, in between screen refreshes. By default android main thread has a set of regular responsabilities such as:

  • always parse xml
  • inflate view components
  • draw them again and again for every refresh
  • attend click events

If we add more tasks to the main thread, if its execution time exceeds this super small time gap between two refreshes, the app will show performance errors. The Screen might freeze. As a result of this, we should always implement long running tasks async in a separated thread.

Coroutines vs Threads

Couroutines are NOT the same as Threads. We have Main or UI Thread. We also have on demand back ground worker threads, but threads are not equal to coroutines. Any of those threads can have many coroutines running in it, at the same time.

Basic Implementation

We need to implement both core and Android dependencies.

dependencies {
	implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-RC2"
	implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0-RC2"
}

Let’s start with CoroutineScope. This is an interface which provides the scope of the coroutine. We need to run the task in a background thread, so we provide Dispatchers.IO. launch{} is the coroutine builder.

btnDownload.setOnClickListener {
	CoroutineScope(Dispatchers.IO).launch {
		downloadData()
	}
}

The function downloadData() will execute inside our new coroutine.

Coroutines Scope

In an App we can launch hundreds coroutines running at the same time. By default, coroutines don’t help us to keep track of them or any work that’s being done by them. If we don’t manage them carefully, there can be leaks in memory. To solve this, we must start all coroutines within a scope. Using the scope, we can easily keep track of coroutines, cancel them and handle errors or exceptions. We do this through the CoroutineScope interface.

There’s another interface called GlobalScope (very rarely used) which is used to launch top-level coroutines, which are operating on the whole application lifetime.

Coroutines Dispatchers

Both of these scopes also acts as a reference to the coroutine context. (Dispatchers.IO). We can include the name of a Job instance, plus a Dispatcher as the context of the job. This describes the kind of thread where the coroutine should be run. It’s recommended to start coroutines using Main Thread and then switch to Background Threads.

We have:

  • Dispatchers.Main - coroutine will run in the Main Thread (also called UI Thread). There’s only one, so execute only lightweight tasks.
  • Dispatchers.IO - coroutine will run in a background thread from a shared pool of on-demand created threads. We use this to work with local database, networking or work with files.
  • Dispatchers.Default - for CPU intensive stasks such as sorting a large list.
  • Dispatchers.Unconfined - coroutine will run in the current thread, but if it’s suspended and resumed it will run on suspending function’s thread. This is used with GlobalScope. It’s not recommended for Android Development.

We may as well create our own executors and dispatchers. Most of the time, we should use Main and IO.

Coroutines Builders

The following snippet .launch{} is the coroutine builder. This are extension functions of coroutine scopes.
There are four main builders:

  • launch - this launches a new coroutine without blocking the current thread. It returns an instance of Job, which can be used as a reference to the coroutine or to cancel it. We use this for coroutines that doesn’t have any result as the return value.
  • async - it’s used to get a result value. It allows us to launch coroutines in parallel without blocking the current thread. This returns an instance of Deferred<T>. We need to invoke await() to get the value. This are the ones we use most in Android Development.
  • produce - Is for coroutines which produces a Stream of elements. Returns an instance of ReceiveChannel.
  • runblocking - Used mostly for testing. It will block the thread while the coroutine is executing. It returns a result of type T.

Switch the Thread of a Coroutine

In Android, we cannot directly call to a view component running in the UI Thread from a Background Thread. This will crash with a CalledFromWrongThreadException.

THIS WON’T WORK

btnDownload.setOnClickListener {
	CoroutineScope(Dispatchers.IO).launch {
		downloadData()
	}
}

// executed in a coroutine
private fun downloadData() {
	for (i in 1..20000) {
		// THIS WON'T WORK.
		textView.text = "Downloading user $i in ${Thread.currentThread().name}"
	}
}

Only the original thread that created a view hierarchy can touch its views, therefore we have to call views from the UI Thread. We add withContext() and suspend to the function’s args, as withContext is a suspending function. We cannot call a suspending function from a normal function.

// executed in a coroutine
private suspend fun downloadData() {
	withContext(Dispatchers.Main) {
		for (i in 1..20000) {
			textView.text = "Downloading user $i in ${Thread.currentThread().name}"
		}
	}
}

Suspending Functions

Whenever a coroutine is suspended, the current stack frame of ther function is copied and saved into memory. When the function resumes after completing its task, the stack frame is copied back from where it was saved and starts running again.

Other libraries such as room and retrofit, also provide suspending functions support.

These are some of those suspending functions: withContext, withTimeout, withTimeoutOrNull, join, delay. If we’re going to call them from our own functions, we have to mark our own functions as suspend fun. A suspending function can only be called from a coroutine block or from another suspending function. suspending means a function with a heavy long running task.

A suspending function doesn’t block a thread. It only suspends the coroutine itself. That thread is returned to the pool while the coroutine is waiting, and when the waiting is done the coroutine resumes on a free thread in the pool.

Async Builder and Await

Writing code to download data in parallel and combine them at the end is called parallel decomposition. To do it, we need to use async coroutine builder. This returns and instance of Deferred. We can use it by invoking its await function.

fun asyncTest() {
	CoroutineScope(Main).launch {
		val stock1 = async(IO) {getStock1()}
		val stock2 = async(IO) {getStock2()}
		val total = stock1.await() + stock2.await()
	}
}

private suspend fun getStock1() {
	// ...
}

Since this coroutine runs on the Main Thread, we could add a Toast message or interact with the view.

Unstructured Concurrency

Unstructured concurrency does not guarantee to complete all the tasks of the suspending function, before it returns. There’re situations we need to launch multiple coroutines concurrently in a suspending function and get some result returned from the function.

This is the wrong way to do it!.

class UserDataManager {
	suspend fun getTotalUserCount(): Int {
		var count = 0
		
		CoroutineScope(Dispatchers.IO).launch {
			delay(1000)
			count = 50
		}
		
		return count
	}
}

This will return 0 instead of 50, as it doesn’t wait for the async task. The child coroutines can be still running, even after the completion of the parent coroutine.

Structured Concurrency

Structured concurrency guarantees to complete all the work started by coroutines within the child scope before the return of the suspending function. It helps us to keep track of tasks we started and to cancel them when needed.

All previous problems can be solved using the suspending function coroutineScope. It allows us to create a child scope, within a given coroutine scope. This guarantees the completion of the tasks when the suspending function returns.

class UserDataManager {
	var count = 0
	lateinit var deferred: Deferred<Int>
	
	suspend fun getTotalUserCount(): Int {
		coroutineScope {
			launch {
				delay(1000)
				count = 50
			}
		}
	
		deferred = async(IO) {
			delay(3000)
			return@async 70
		}
		
		return count + deferred.await()
	}
}

Coroutines patterns with ViewModel and LiveData

For either ViewModelScope and LifeCycleScope we need to implement the following libraries.

def arch_version = '2.2.0-alpha04'
implementation 'androidx.lifecycle:lifecycle-extensions:$arch_version'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:$arch_version'

ViewModelScope

We have a ViewModel class. We define the coroutine scope we want to use and the method where we call it from.

In Android everytime a ViewModel is cleared from memory, just before cleaning it invokes its onCleared() method.

Some of the coroutines we launch in a ViewModel have the potential to run even after the ViewModel is cleared from memory. It might run until app termination. If this is not intended, we will end up leaking memory. To avoid this, we need to clear the coroutine.

class MainActivityViewModel: ViewModel() {

	// allows us to control all coroutines launched in this scope
	private val myJob = Job()
	private val myScope = CoroutineScope(Dispatchers.IO + myJob)

	fun getUserData() {
		myScope.launch {
			// write code. 
		}
	}

	override fun onCleared() {
		super.onCleared()
		// this is useful to cancel the coroutines for a couple of ViewModels
		myJob.cancel()
	}

}

If we have 20 ViewModels, there’s a better alternative with viewModelScope as this may be a wasting of time. This is bounded to ViewModel’s lifecycle. It was created to automatically handle cancellation when the ViewModel’s onClear() is called.

viewModelScope is a CoroutineScope tied to a ViewModel. It needs the ktx dependency in gradle.

Now, for the previous ViewModel, we can simply use viewModelScope instead of myScope. Clearing will be done automatically if the ViewModel is cleared.

class MainActivityViewModel: ViewModel() {

	fun getUserData() {
		viewModelScope.launch {
			// write code. 
		}
	}

}

LifeCycleScope

A lifecycleScope is defined for each Lifecycle Object. All coroutines launched in this scope are canceled when the Lifecycle is destroyed. You can access the CoroutineScope either via lifecycle.coroutineScope or lifecycleOwner.lifecycleScope. This scope is for objects with a lifecycle, such as Activities and Fragments.

If it’s in an Activity or a Fragment, all the coroutines will be canceled when the onDestroy() method is called.

class MainActivity: AppCompatActivity() {

	override fun onCreate(savedInstanceState: Bundle?) {
		// ...
		
		lifecycleScope.launch {
			// do whatever with coroutines. 
		}
	}

}

Sometimes we might need to suspend execution of a code block, considering the current state of a lifecycle object. For that we have three additional builders.

  • lifecycleScope.launchWhenCreated - if you have long running operations which should happen only once during the lifecycle of the activity or fragment. This will be called when it’s created for the first time.
  • lifecycleScope.launchWhenStarted - this will launch when the activity or fragment started. For some operations we need our Fragment lifecycle to be at least started.
  • lifecycleScope.launchWhenResumed - this is the state in which the App interacts with the user.

LiveData Builder

Example Without LiveData Builder

We have the following data class with two properties.

data class User(val id: Int, val name: String)

Then we create a repository class.

class UserRepository {
	suspend fun getUsers(): List<User> {
		// implement
	}
}

We also need a ViewModel class. with a viewModelScope which launches a new coroutine. Then we have a withContext() which switches the thread of the coroutine to a background thread.

class MainActivityViewModel: ViewModel() {
	private var userRepository = UserRespository()
	var users: MutableLiveData<List<User>> = MutableLiveData()
	
	fun getUsers() {
		viewModelScope.launch {
			var result: List<User>? = null
			withContext(Dispatchers.IO) {
				result = userReppository.getUsers()
			}
			users.value = result
		}
	}
}

This is the MainActivity class. We invoke the previous function and observe the list of users.

override fun onCreate(savedInstanceState: Bundle?) {
	...
	
	mainActivityViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
	mainActivityViewModel.getUsers()
	mainActivityViewModel.users.observe(this, Observer { myUsers ->
		myUsers.forEach {
			Log.i("MyTag", "name is ${it.name}")
		}
	})
}

With LiveData Builder

To use this we need to add the following dependency.

def arch_version = '2.2.0-alpha04'
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$arch_version"

There’s a new block which will automatically execute when LiveData becomes active. It automatically decides when to stop executing and cancel the coroutines inside the building block considering the state of the lifecycle owner.

Inside the LiveData building block, you can use emit() function to set a value to LiveData.

This is the previous LiveModel. Remember long running tasks need to be executed in a background thread.

class MainActivityViewModel: ViewModel() {
	private var userRepository = UserRespository()
	
	var users = liveData(Distpatchers.IO) {
		val result = usersRepository.getUsers()
		emit(result)
	}
	
}

With this change, we don’t need to invoke the function in MainActivity anymore.

override fun onCreate(savedInstanceState: Bundle?) {
	...
	
	mainActivityViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
	mainActivityViewModel.users.observe(this, Observer { myUsers ->
		myUsers.forEach {
			Log.i("MyTag", "name is ${it.name}")
		}
	})
}

Read More

Android Room Data Persistance (SQLite)

SQLite is the most used database for mobile Apps. Room Data Persistance is a library introduced in 2017 to help with this task. It uses annotations to generate boilerplate code.

When we use Room in a project, we need to create 3 types of Room specific classes.

  • A room @Database class, which represents the actual sqlite database.
  • An @Entity class, which represents the tables.
  • DAO interfaces for data access methods.

Implementation

Let’s modify our build.gradle and add kapt for annotations processor. We also add the dependencies for room, databinding, viewmodel, livedata and coroutines.

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
	buildFeatures {  
		dataBinding = true  
	}
}

dependencies {
	def lifecycle_version = "2.2.0"
	def room_version = "2.2.3"
	
	// annotation processor
	kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
	
	// viewmodel
	implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
	
	// livedata
	implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
	
	// sqlite room dependency
	implementation "androidx.room:room-runtime:$room_version"
	kapt "androidx.room:room-compiler:$room_version"
	
	// coroutines support for room
	implementation "androidx.room:room-ktx:$room_version"
	
	// coroutines support in Android
	implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3"
	implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"
}

Entity Classes

We’re going to create the following table subscriber_data_table. It contains three columns.

  • subscriber_id (primary key)
  • subscriber_name
  • subscriber_email

This is its class representation.

@Entity(tableName = "subscriber_data_table")
data class Subscriber(

	@PrimaryKey(autoGenerate = true)
	@ColumnInfo(name = "subscriber_id")
	val id: Int,
	
	@ColumnInfo(name = "subscriber_name")
	val name: String,
	
	@ColumnInfo(name = "subscriber_email")
	val email: String
	
)

Data Access Object (DAO) Interface

We have the following DAO class. The function’s name is not important. We only need the @Insert annotation. The method has suspend modifier because room doesn’t support database access on the main thread as it might lock the UI for a long time. The easiest way to solve this are kotlin coroutines.

For verification we might need to get a return value. We can get the new rowIds as the return value.

As parameters, OnConflictStrategy is an important one. REPLACE will try to insert the value, and replace it if an existing matchine one if found. On the other hand IGNORE will just ignore the new one.

With @Query annotation, room allows us to include a SQL query that would run when the function is called. This query will be verified at compile time.

Room facilitates us to get data from a database table as a LiveData of list of entities. These queries are called Async queries because for them, which have a LiveData as a return value, room always runs them on a background thread by itself, so we don’t have to write special code to use coroutines.

@Dao
interface SubscriberDAO {

	@Insert(onConflict = OnConflictStrategy.REPLACE)
	suspend fun insert(subscriber: Subscriber): Long

	@Insert
	suspend fun insert(subscribers: List<Subscriber>): List<Long>

	@Update
	suspend fun update(subscriber: Subscriber)
	
	@Update
	suspend fun update(subscribers: List<Subscriber>)
	
	@Delete
	suspend fun delete(subscriber: Subscriber)
	
	@Delete
	suspend fun delete(subscribers: List<Subscriber>)

	@Query("DELETE FROM subscriber_data_table")
	suspend fun deleteAll()

	// this isn't a suspend function!
	@Query("SELECT * FROM subscriber_data_table")
	fun getAllSubscribers(): LiveData<List<Subscriber>>

}

Database class

At @Database we need to provide the list of entity classes, then the database version number. This is important when we are migrating the database from one version to another. Then we need to declare an abstract reference for the DAO interface.

Usually we should only use one instance of a room database for the entire App. In Kotlin we create singletons as companion objects.

@Database(entities = [Subscriber::class], version = 1)
abstract class SubscriberDatabase: RoomDatabase() {

	abstract val subscriberDAO: SubscriberDAO

	companion object {
		@Volatile
		private var INSTANCE: SubscriberDatabase? = null
			fun getInstance(context: Context): SubscriberDatabase {
				synchronized(this) {
					var instance = INSTANCE
					if(instance==null) {
						instance = Room.databaseBuilder(
							context.applicationContext,
							SubscriberDatabase::class.java,
							"subscriber_data_database"
						).build()
					}
					return instance
				}
			}
	}

}

Repository in MVVM Architecture

Model in MVVM means all data management related components. It has local database related components, remote data sources related components and a repository.

The purpose of a repository class it to provide a clean API for ViewModels to easily get and send data. You can think of repositories as mediators between different data sources, such as local databases, web services and caches.

class SubscriberRepository(private val dao: SubscriberDao) {
	
	// this already is LiveData
	val subscribers = dao.getAllSubscribers()
	
	suspend fun insert(subscriber: Subscriber) {
		dao.insert(subscriber)
	}
	
	suspend fun update(subscriber: Subscriber) {
		dao.update(subscriber)
	}
	
	suspend fun delete(subscriber: Subscriber) {
		dao.delete(subscriber)
	}
	
	suspend fun deleteAll(subscriber: Subscriber) {
		dao.deleteAll(subscriber)
	}
}

ViewModel and DataBinding

We’re going to use DataBinding in a ViewModel with its related View. The view will contain two fields that will be updated to show the entity values and the view will contain EditText for those two values, and two buttons that will change dynamically.

class SubscriberViewModel(private val repository: SubscriberRepository): ViewModel(), Observable {
	
	val subscribers = repository.subscribers
	
	@Bindable
	val inputName = MutableLiveData<String>()
	
	@Bindable
	val inputEmail = MutableLiveData<String>()
	
	@Bindable
	val saveOrUpdateButtonText = MutableLiveData<String>()
	
	@Bindable
	val clearAllOrDeleteButtonText = MutableLiveData<String>()
	
	init {
		saveOrUpdateButtonText.value = "Save"
		clearAllOrDeleteButtonText.value = "Clear All"
	}
	
	fun saveOrUpdate() {
		val name = inputName.value!!
		val email = inputEmail.value!!
		// since the ID is AutoGenerated we can just set 0 and it will be ignored
		insert(Subscriber(0, name, email))
		inputName.value = null
		inputEmail.value = null
	}
	
	fun clearAllOrDelete() {
		clearAll()
	}
	
	// this will be executed in a background thread
	fun insert(subscriber: Subscriber) = viewModelScope.launch {
		repository.insert(subscriber)
	}
	
	fun update(subscriber: Subscriber) = viewModelScope.launch {
		repository.update(subscriber)
	}
	
	fun delete(subscriber: Subscriber) = viewModelScope.launch {
		repository.delete(subscriber)
	}
	
	fun clearAll() = viewModelScope.launch {
		repository.deleteAll()
	}
	
	override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
		
	}
	
	override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
	
	}
	
}

This is the related view.

<layout>
	<data>
		<variable
			  name="myViewModel"
			  type="es.codes.mario.templates.SubscriberViewModel"/>
	</data>
	<LinearLayout>
		<EditText
			...
		    android:text="@={myViewModel.inputName}"/>
		<EditText
		  	...
		  	android:text="@={myViewModel.inputEmail}"/>
		<Button
			...
			android:text="@={myViewModel.saveOrUpdateButtonText}"
			android:onClick="@{()->myViewModel.saveOrUpdate()}"/>
		<Button
			...
			android:text="@={myViewModel.clearAllOrDeleteButtonText}"
			android:onClick="@{()->myViewModel.clearAllOrDelete()}"/>
		...
	</LinearLayout>
</layout>

Then we will create a Factory for our ViewModel.

class SubscriberViewModelFactory(private val repository: SubscriberRepository): ViewModelProvider.Factory {
	
	// this is boilerplate code for all Factories
	override fun <T: ViewModel?> create(modelClass: Class<T>): T {
		if(modelClass.isAssignableFrom(SubscriberViewModel::class.java)) {
			return SubscriberViewModel(repository) as T
		}
		throw IllegalArgumentException("Unknown View Model class")
	}
	
}

This is the code at Main that binds everything.

class MainActivity: AppCompatActivity() {
	
	private lateinit var binding: ActivityMainBinding
	private lateinit var subscriberViewModel: SubscriberViewModel
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
		val dao = SubscriberDatabase.getInstance(application).subscriberDAO
		val repository = SubscriberRepository(dao)
		val factory = SubscriberViewModelFactory(repository)
		subscriberViewModel = ViewModelProvider(this, factory).get(SubscriberViewModel::class.java)
		binding.myViewModel = subscriberViewModel
		// this is for livedata usage with data binding
		binding.lifecycleOwner = this
		displaySubscribersList()
	}
	
	private fun displaySubscribersList() {
		subscriberViewModel.subscribers.observe(this, Observer {
			Log.i("MyTAG", it.toString())
		})
	}
	
}

Displaying Toast with ViewModel and LiveData

This way we display messages through Toast when methods inside the ViewModel are executed. The ViewModel shouldn’t know any detail about the View.

Copy the following Event class as is.

open class Event<out T>(private val content: T) {
        var hasBeenHandled = false
                private set // allows external read but not write
                
        /**
         * Returns the content and prevents its use again
         */
        fun getContentIfNotHandled(): T? {
                return if(hasBeenHandled) {
                        null
                } else {
                        hasBeenHandled = true
                        content
                }
        }
        
        /**
         * Returns the content, even if it's already been handled
         */
        fun peekContent(): T = content
}

Now do this inside ViewModel

class ViewModel {
        // this is MutableLiveData so we can edit its value, but it's private so we only can access it from this class. 
        private val statusMessage = MutableLiveData<Event<String>>()
        
        val message: LiveData<Event<String>>
                get() = statusMessage
                
        fun insert() {
                ...
                statusMessage.value = Event("subscriber has been inserted")
        }
        
        fun delete() {
                ...
                statusMessage.value = Event("subscriber has been deleted")
        }
}

You use it like this, at the class you’re going to use your ViewModel at.

~~~ kotlin class MainActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { val viewModel = …

            ...
            
            viewModel.message.observe(this, Observer {
                    it.getContentIfNotHandled()?.let {
                            Toast.makeText(this, it, Toast.LENGTH_LONG).show()
                    }
            })
    } }

Read More

Android Recycler View

The old way to display data was ListViews and GridViews, but they were very much memory inefficient. RecyclerView is a much more memory efficient, more advanced version.

RecyclerView without Data Binding (Kotlin Synthetics)

Go to activity_main.xml and put a new RecyclerView into the top ConstraintLayout. For the example, we changed its id to my_recycler_view.

Now go to MainActivity. This way of using directly the ID is called Kotlin synthetics. It’s useful to have concise code for smaller projects, instead of using findViewById or Data Binding. This is not recommended for larger, complex projects. Use Data Binding instead.

Set a layout manager for our recycler view. There’re 3 of them (we may create our own custome layout managers)

  • LinearLayoutManager - standard listviews
  • GridLayoutManager - display items on a grid
  • StaggeredGridLayoutManager - position list items in a saggered grid

Then we set an adapter instance for the recycler view. An adapter class should be a subclass of RecyclerView library’s adapter class. We will set our MyRecyclerViewAdapter as the adapter of the RecyclerView.

class MainActivity: AppCompatActivity() {
	val fruitsList = listOf(Fruit("Mango", "Tom"), Fruit("Apple", "Mario"), Fruit("Banana", "Carlos"))
	
	override fun onCreate(savedInsanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)
		
		my_recycler_view.setBackgroundColor(Color.YELLOW)
		// this 2 lines here
		my_recycler_view.layoutManager = LinearLayoutManager(this)
		my_recycler_view.adapter = MyRecyclerViewAdapter(fruitsList)
	}
}

In most practical situations we need to send a list of objects to the adapter and display their values in the recycler view. To do this we need a Data class.

data class Fruit(val name: String, val supplier: String)

This is our customly created Adapter for our ViewHolder.
An adapter class has three main functions which we need to implement.

Create the resource file we need right click -> new -> android resource file -> file name: list_item; resource type: layout -> OK. Convert it to a ConstraintLayout.

// this 2 classes go together in a single Kotlin file. 
class MyRecyclerViewAdaper(val fruitsList: List<Fruit>): RecyclerView.Adapter<MyViewHolder>() {

	// this is where we create the list item xml template.
	override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyviewHolder {
		// get layout inflator and using that, inflate the list item.
		val layoutInfater = LayoutInflater.from(parent.context)
		val listItem = layoutInflater.inflate(R.layout.list_item, parent, false)
		return MyViewHolder(listItem)
	}

	// returns the total number of items in the dataset held by the adapter. 
	// RecyclerView will create a number of spots for list items considering this value
	override fun getItemCoun(): Int {
		return fruitsList.size; // we will see 5 rows. 
	}

	// we use this to display data on the list item. 
	// the position param represents the position between 0 and getItemCount()
	override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
		val fruit = fruitsList[position]	
		holder.bind(fruit)
	} 

}

// we use this to receive data from onBindViewHolder()
class MyViewHolder(val view: View): RecyclerView.ViewHolder(view)  {

	fun bind(fruit: Fruit) {
		// name_text_view is the name of a TextView we use to show
		view.name_text_view.text = fruit.name
	}

}

Add a click listener to a Recycler View

We will use a lambda to abstract this function.
On MainActivity we define a new method which is a Fruit consumer. Then we pass it to MyRecyclerViewAdapter as a param arg, and from here to MyViewHolder where the click listener is defined.

class MainActivity: AppCompatActivity() {
	val fruitsList = listOf(Fruit("Mango", "Tom"), Fruit("Apple", "Mario"), Fruit("Banana", "Carlos"))
	
	override fun onCreate(savedInsanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)
		
		my_recycler_view.setBackgroundColor(Color.YELLOW)
		// this 2 lines here
		my_recycler_view.layoutManager = LinearLayoutManager(this)
		my_recycler_view.adapter = MyRecyclerViewAdapter(fruitsList, {
			selectedFruitItem: Fruit -> listItemClicked(selectedFruitItem)
		})
	}
	
	private fun listItemClicked(fruit: Fruit) {
		Toast.makeText(this@MainActivity,
			"Supplier name is ${fruit.supplier}",
			Toast.LENGTH_LONG).show()
	}
}
// this 2 classes go together in a single Kotlin file. 
class MyRecyclerViewAdaper(private val fruitsList: List<Fruit>, private val clickListener:(Fruit)->Unit): RecyclerView.Adapter<MyViewHolder>() {

	// this is where we create the list item xml template.
	override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyviewHolder {
		// get layout inflator and using that, inflate the list item.
		val layoutInfater = LayoutInflater.from(parent.context)
		val listItem = layoutInflater.inflate(R.layout.list_item, parent, false)
		return MyViewHolder(listItem)
	}

	// returns the total number of items in the dataset held by the adapter. 
	// RecyclerView will create a number of spots for list items considering this value
	override fun getItemCoun(): Int {
		return fruitsList.size; // we will see 5 rows. 
	}

	// we use this to display data on the list item. 
	// the position param represents the position between 0 and getItemCount()
	override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
		val fruit = fruitsList[position]	
		holder.bind(fruit, clickListener)
	} 

}

// we use this to receive data from onBindViewHolder()
class MyViewHolder(val view: View): RecyclerView.ViewHolder(view)  {

	fun bind(fruit: Fruit, clickLisener:(Fruit->Unit)) {
		// name_text_view is the name of a TextView we use to show
		view.name_text_view.text = fruit.name
		view.setOnClickListener {
			clickListener(fruit)
		}
	}

}

Read More

Android Navigation Architecture Component

The new trend in android dev is single activity, multiple fragments model. When we navigate, we usually create a single empty activity which acts as a basement. All other screens of the app will be created using fragments. This is the recommended best practice by Google android team.

There are three main parts of a navigation component.

  • Navigation graph - XML resource file that contains all navigation-related information. This allows us to manage navigation related tasks from a single location.
  • NavHostFragment - Empty container we keep on the activity to hold the navigation graph.
  • NavController - Class generated by the navigation library to manage the navigation between destinations we added to the navigation graph.

Dependencies

Add navigation dependencies, safeargs and data binding.

apply plugin: "androidx.navigation.safeargs.kotlin"

android {
	dataBinding {
		enabled = true
	}
}

dependencies {
	def nav_version = "2.1.0"
	
	implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
	implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}

Remember to add <layout> tags to the outer most tag in .xml files.
Add this classpath in your top level build.gradle.

dependencies {
	def nav_version = "2.1.0"
	
	classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}

(add photo of nav_graph)

It shows all the App’s screens and how the user can reach them, in a single location. They’re called destinations. It allows us to manage all the navigated related actions.

To create it, right click in the app folder > new > android resource file > file name: nav_graph; resource type: navigation. It will appear under a new folder navigation

When created, there’s no destination nor NavHostFragments yet.

To host the navigation graph, we need a host fragment. Navigation graph connects to other parts of the app through this host fragment.

To add a NavHostFragment, go to activity_main.xml, designer mode, select Containers, drag NavHostFragment. It automatically recognizes the navigation graph we created previously. Click on it. Click on infer constraints.

(add photo NavHostFragment1)

Under the hood, this is just a newly added fragment, with some special properties.

Now it should show the host on nav_graph.xml.

(add photo NavHostFragment2)

Different fragments in the nav graph are called navigation destinations. Click on *add a destination* > Create new destination > Fragment Name: HomeFragment > Accept.

This will:

  • Create a new HomeFragment.kt class.
  • Create a new fragment_home.xml layout resource.
  • Add it to nav_graph.xml
  • Add a placeholder to strings.xml

Inside nav_graph.xml it will contain a home symbol. This indicates start destination. If you doule click on the fragment’s representation, this takes you to its fragment_home.xml. Since ConstraintLayout is much easier to work with, replace the main Container with it. right click on FrameLayout > Convert FrameLayout to ConstraintLayout > OK. If you want to use DataBinding, remember to surround it by <layout> tags.

Add a new destination

In nav_graph.xml select the graph you want to be the base. Click on New Destination > Create new Destination > Fragment Name: SecondFragment; unclick options > OK.

Now we have two destinations.

We have two empty fragments: HomeFragment and SecondFragment. Go to nav_graph.xml. Select HomeFragment, hover the mouse over it and drag the circle it will appear to its SecondFragment. This creates an action with a given id action_homeFragment_to_secondFragment.

To execute this action on the click of a button, go to your HomeFragment (using DataBinding) and set the onClickListener action.

class HomeFragment: Fragment() {
	private lateinit var binding: FragmentHomeBinding
	
	override fun onCreateView(
		inflater: LayoutInflater, container: ViewGroup?,
		savedInstanceState: Bundle?
	): View? {
		binding = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false)
		// this here
		binding.button.setOnClickListener {
			it.findNavController().navigate(R.id.action_homeFragment_to_secondFragment)
		}
		return binding.root
	}
}

Transfer data between destinations

It’s not recommended to pass data beween destinations. The best practice is using a view model and get the data from the view model, but Android Navigation architecture component allows us to pass data.

In the HomeFragment we have an EditText with id editText to get the user input and a submit Button. In the SecondFragment we have a TextView. We will display the user input from HomeFragment into SecondFragment.

Starting point for HomeFragment.

class HomeFragment: Fragment() {
	private lateinit var binding: FragmentHomeBinding
	
	override fun onCreateView(
		inflater: LayoutInflater, container: ViewGroup?,
		savedInstanceState: Bundle?
	): View? {
		binding = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false)
		binding.button.setOnClickListener {
			it.findNavController().navigate(R.id.action_homeFragment_to_secondFragment)
		}
		return binding.root
	}
}

We get the user input as a bundle. Then we pass tha bundle as the second argument of this navigate function.

class HomeFragment: Fragment() {
	private lateinit var binding: FragmentHomeBinding
	
	override fun onCreateView(
		inflater: LayoutInflater, container: ViewGroup?,
		savedInstanceState: Bundle?
	): View? {
		binding = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false)
		binding.button.setOnClickListener {
			// this here. The if avoids possible null pointers.
			if(!TextUtils.isEmpty(binding.editText.text.toString())) {
				val bundle = bundleOf("user_input" to binding.editText.text.toString())
				it.findNavController().navigate(R.id.action_homeFragment_to_secondFragment, bundle)
			}
		}
		return binding.root
	}
}

Starting point for SecondFragment.

 class SecondFragment: Fragment() {
 	private lateini var binding: FragmentSecondBinding
	
	override fun onCreateView(
		inflater: LayoutInflater, container: ViewGroup?.
		savedInstanceState: Bundle?
	): View? {
		// inflate the layout for this fragment
		binding = DataBindingUtil.inflate(inflate, R.layout.fragment_second, conainer, false)
		return binding.root
	}
 }

We have added the bundle as an argument, so here we need to retrieve it.

 class SecondFragment: Fragment() {
 	private lateini var binding: FragmentSecondBinding
	
	override fun onCreateView(
		inflater: LayoutInflater, container: ViewGroup?.
		savedInstanceState: Bundle?
	): View? {
		// inflate the layout for this fragment
		binding = DataBindingUtil.inflate(inflate, R.layout.fragment_second, conainer, false)
		
		// this here
		var input: String? = arguments!!.getString("user_input"  )
		binding.textView.ext = input.toString()
		
		return binding.root
	}
 }

## Animations for Actions Go to navigation_graph.xml and select an action action_homeFragment_to_secondFragment. There’s a block Animations where you may select animations for Enter, Exit, Pop Enter, Pop Exit.
When we navigate from one destination to anoher, the new screen will appear with Enter animation, and the previous window will disspear with Exit animation. When the user clicks on the back button of he phone (the screen navigates backwards), the current screen will disappear with Pop Exit and the previous window will appear with the Pop Enter animation.

You may search for animations, such as this here.

Take one of them and create a resource file Right click on project > new > Android Resource File > File name: slide_in_left; Resource Type: Animation. Copy and paste the animation you want.

With the animations already created, select the one you want for the action.

Read More

ViewModel & LiveData with DataBinding

Integrate ViewModel with DataBinding

This avoids us the need to explicitly declare .setOnClickListener() manually and integrate it into the view.

Up until now, this was our MainActivity following MVVM architecture.

class MainActivity: AppCompatActivity() {

	private lateinit var viewModel: MainActivityViewModel
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
		viewModel = ViewModelProviders.of(this).get(MainActivityViewMode::class)
		
		viewModel.count.observe(this, Observer {
			binding.countText.text = it.toString()
		})
		
		// we will remove methods such as this one
		binding.button.setOnClickListener {
			viewModel.updateCount()
		}
	}
}

We’re going to connect the ViewModel object with the DataBinding object and declare the method directly inside the .xml.

Open the layout activity_main.xml, for this case, and add <data> tags to assign the view model Object we created there. Be careful as the .xml layouts need to be surrounded by <layout> tags.

<layout>
	<data>
		<variable
		  name="myViewModel"
		  type="es.codes.mario.templates.MainActivityViewModel"/>
	</data>
</layout>

We go to the buttons declaration inside the same .xml file and change the onClick="" variable. This will bind the expression.
(!) To do this, our gradle version has to be higher than 2.0 (!)

<Button 
	...
	android:onClick="@{()->myViewModel.updateCount()}"/>

Now we go back to our class, add the code to bind it, and remove the code for the listener.

class MainActivity: AppCompatActivity() {
	private lateinit var viewModel: MainActivityViewModel
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
		viewModel = ViewModelProviders.of(this).get(MainActivityViewMode::class)
		
		// add this here to bind it
		binding.myViewModel = viewModel
		
		viewModel.count.observe(this, Observer {
			binding.countText.text = it.toString()
		})
	
		// delete setOnClickListener method
	}
}

Integrate LiveData with DataBinding

This avoids us the need to explicitly declare the Observer.

class MainActivity: AppCompatActivity() {
	private lateinit var viewModel: MainActivityViewModel
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
		viewModel = ViewModelProviders.of(this).get(MainActivityViewMode::class)
		binding.myViewModel = viewModel
		
		// now we will remove methods such as this one
		viewModel.count.observe(this, Observer {
			binding.countText.text = it.toString()
		})
	
	}
}

Instead we will use LiveData as a DataBinding source in the .xml layout file.

We want to display the following count variable, but it’s an Int. To display it, we need to convert that Int to String.

class MainActivityViewModel: ViewModel() {
	var count = MutableLiveData<Int>()
	...
}

We go to activity_main.xml and declare android:text tag. Then we will use LiveData directly as the DataBinding source.

<TextView
	...
	android:text="@{String.valueOf(myViewModel.count)}"
	... />

There’s one special thing left to do. LiveData is always associated with the lifecycle of an activity or a service, so we have to provide the actual lifecycle owner to the view model object. In MainActivity we need to set the current activity as the lifecycle owner of the binding object.

class MainActivity: AppCompatActivity() {
	private lateinit var viewModel: MainActivityViewModel
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
		viewModel = ViewModelProviders.of(this).get(MainActivityViewMode::class)
		
		// add this
		binding.lifecycleOwner = this
		binding.myViewModel = viewModel
	}
}

encapsulate data

If we want to encapsulate data for more security, we may declare it as private and create a public variable to access it.

class MainActivityViewModel: ViewModel() {
	private var count = MutableLiveData<Int>()
	val countData: LiveData<Int>
	get() = count
	
	...
}

We must also change the .xml value.

<TextView
	...
	android:text="@{String.valueOf(myViewModel.countData)}"
	... />

Two way data binding

We use one-way DataBinding to show the user some data or get user input.

With two way data binding when the value of the object changes, the UI changes, and when the UI changes, the value of the object changes.

We have the following ViewModel.

class MainActivityViewModel: ViewModel() {
	val userName = MutableLiveData<String>()
	
	init {
		userName.value = "Frank"
	}
}

Open main_activity.xml and surround the existing contents by <layout> tags.
Write <data> tags to reference MainActivityViewModel.
Bind the value of userName MutableLiveData to the TextView.

<layout>
	<data>
		<variable
			name="viewModel"
			type="es.codes.mario.templates.MainActivityViewModel"/>
	</data>
	<android.constraintlayout.widget.ConstraintLayout>
		<TextView
			...
			android:text="@{viewModel.userName}"
			... />
	</android.constraintlayout.widget.ConstraintLayout>
</layout>

Go to MainActivity and

  1. Define a DataBinding object.
  2. Replace setContentView().
  3. Define a reference variable for the view model.
  4. Since we are using livedata with data binding, set this activity as the lifecycle owner.
class MainActivity: AppCompatActivity() {

	private lateinit var binding: ActivityMainBinding
	private lateinit var viewModel: MainActivityViewModel
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
		viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
		binding.viewModel = viewModel
		binding.lifecycleOwner = this
	}
	
}

Up until here this is one way data binding. The view displays LiveData values.

For two way data binding, bind the data in an EditText the same way, but use the expression @={} instead of @{}.

<layout>
	<data>
		<variable
			name="viewModel"
			type="es.codes.mario.templates.MainActivityViewModel"/>
	</data>
	<android.constraintlayout.widget.ConstraintLayout>
		<TextView
			...
			android:text="@{viewModel.userName}"
			... />
		
		<EditText
			...
			android:text="@={viewModel.userName}"
			... />
	</android.constraintlayout.widget.ConstraintLayout>
</layout>

Read More

Android LiveData

We use LiveData to observe data inside ViewModel from an Activity or Fragment. If any change or update to this data happens, we can automatically update the Activity or Fragment.

LiveData only updates observers in an active lifecycle state. Its benefits are to automatically update the UI when app data changes.

LiveData vs MutableLiveData

Data inside LiveData is read-only. It cannot be updated as it has no public method to modify it. MutableLiveData can be updated.

Implementation

Add the following into your App’s gradle.build

dependencies {
	def lifecycle_version = "2.1.0"
	implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
}

We have the following ViewModel.

class MainActivityViewModel(startingTotal: Int): ViewModel {
	
	var total = MutableLiveData<Int>()
	
	init {
		total.value = startingTotal
	}
	
	fun setTotal(input: Int) { total.value = input }
	
}

How we use it from another Activity.

override fun onCreate(savedInstanceState: Bundle?) {
	super.onCreate(savedInstanceState)
	
	viewModel = ...
	viewModel.total.observe(this, Observer {
		// this will be executed when MutableLiveData changes. 
		binding.resultTextView.text = it.toString()
	})
	
	button.setOnClickListener { viewModel.setTotal(5) }
}

How to encapsulate LiveData

We want to encapsulate total, as leaving it public is no good design.

  1. We mark it as private.
  2. We need to declare a new public variable. As we’re not going to edit it, it should be LiveData. We can use Kotlin backing property to return LiveData from a getter function.
class MainActivityViewModel(startingTotal: Int): ViewModel {
	private var total = MutableLiveData<Int>()
	val totalData: LiveData<Int>
	get() = total
	
	init {
		total.value = startingTotal
	}
	
	fun setTotal(input: Int) { total.value = input }
	
}

Now we may change the onCreate() method.

override fun onCreate(savedInstanceState: Bundle?) {
	super.onCreate(savedInstanceState)
	
	viewModel = ...
	viewModel.totalData.observe(this, Observer {
		// this will be executed when MutableLiveData changes. 
		binding.resultTextView.text = it.toString()
	})
	
	button.setOnClickListener { viewModel.setTotal(5) }
}

Read More

Android DataBinding

DataBinding

The improvement of DataBinding vs findViewById() is performance and cleanliness.

For larger apps where we have a lot of views, findViewById is way to slow as it has to search through all layouts every time. This doesn’t happen with DataBinding.

The usage of DataBinding eliminates the need for findViewById().

Implementation

Go to build.gradle file and enable it.

android {
	buildFeatures {
		dataBinding = true
	}
}

Read More

Android MVVM Architecture

Model View ViewModel is an android architecture model. It separates data-presentation logic from business logic. It’s designed to store and manage UI-related data.

  • Model - Application data logic. Plain Objects. API processing classes. Databases.
  • View - Screen’s layout. Houses all widgets for displaying information.
  • ViewModel - Business Logic. Object which describes the behaviour of view logic depending on the result of the model work.

We usually create one ViewModel for an activity or a fragment. Sometimes ViewModels can be shared by two or more Activities or Fragments. ViewModel are created when the activity is created, and they live until the activity is cleared from memory. Therefore ViewModel can only hold values which belong to the activity.

Implementation

We will have 3 packages: model, view, and viewmodel.

Inside model, we have data classes such as this one

data class Country(val countryName: String?)

Read More

Android experience

Initialize Widgets in Kotlin

This is how to initialize variables, outside of DataBinding. There’re 3 ways.

class MyClass : AppCompatActivity() {
	// worst. needs to be 'var', nullable and be initialized
	private var button: Button? = null
	
	// a bit better. stills need to be 'var' and later be initialized
	private lateinit var result: EditText
	
	// best. is 'val' a doesn't need to be initialized
	//	  	 it will be initialized when first used. it is thread-safe
	private val operation by lazy { findViewById<TextView>(R.id.operation) }
	
	override fun onCreate(...) {
		button = findViewById<Button>(R.id.button)
		result = findViewById<EditText>(R.id.result)
	}
}

Synthetic properties

From our class, when we know the activity that we’re gonna use, we type import kotlinx.android.synthetic.main.activity_main.*
This way we can use widgets just as any other properties without the need for findViewById().

Activities

An app is made up of activities, which can launch other activities to perform different tasks. These are normally launched when the user clicks a button or activates a menu option.

Android keeps track of which activities you’ve used, and when you tap the back button, it closes the current activity and displays the previous one on the screen.

Inflate a Layout

To inflate a layout just really means that Android takes our xml definition for the layout, then creates all the widgets that we’ve defined in it. It then positions it on screen, taking note of all the constraints, margins and other settings that we’ve defined.

Constraints

They fix widgets in place. Without them, the widget willa appear on the top left corner when you run the app.

The best way is to constraint top elements to the edges of the app and constraint other widgets relatively to other widgets. This way they form groups and the general app layout doesn’t change when the screen size does.

Types of widget constraining

Wrap Content Causes the widget to become as wide or high as it needs to be. No more, no less.
Fixed You set the dp manually.
Match Constraints Expands the widget, as much as needed, to match the constraints.

Baseline Constraints

It’s possible to set a constraint to the baseline of text, on the widgets that support this to another widget. It needs to be manually activated.

Chains

They serve on how to centre a group of widgets. They’re not always appropiate.

We constrain chains to guidelines. Guidelines don’t always show when created, but if you search for them at the component tree, they’re there. Guidelines have properties just as other widgets but they don’t have height or width. A vertical guideline has 0 width and the height from their parent.

You can use them to center elements. If you put a guideline just at the center of the screen, you can then attach elements to it and they’ll be centered at their half of the screen.

Bundle

Bundle is an object that can be used to pass data around, within the Android framework.

When onCreate(savedInstanceState: Bundle?) method starts, it’s given a bundle containing all the data needed to restore it to the state it was in, when it was destroyed. When Android destroys the activity as a result of rotating the device or for a number of other reasons, the InstanceState of the activity is automatically saved before the activity is detroyed.

All of that is taken care of by the activity or AppCompactActivity classes, so because our MainActivity extends AppCompatActivity, we get the advantage of that behaviour.

The contents of the EditText are saved for us, because the contents of any editable widget form part of the activities InstanceState. This only applies to editable word widgets.

Save App State

The methods we need to Override to store state are onSaveInstanceState() and onRestoreInstanceState().

State can be restored in either onCreate() or onRestoreInstanceState() but it’d be a better idea to use the former, to keep things easier to understand.

override fun onSaveInstanceState(outState: Bundle?) {
	super.onSaveInstanceState(outState)
	outState?.putString("MY_STRING", "oh no!")
}

override fun onRestoreInstanceState(savedInstanceState: Bundle) {
	super.onRestoreInstanceState(savedInstance)
	val state: String = savedInstanceState.getString("MY_STRING")
}

Resources folder

Resources in this folder cannot have capital letters in them.

This is because Windows doesn’t make a difference between capital and normal letters for images. So this.jpg and This.jpg would be the same picture.

Permissions

By default, an app has no permissions to anything at all. The first time we run an App, we have to give it the permissions it needs to run. After that first time, the App has to ask for permissions each time it’s run, but instead of the User, the Android system will answer with the granted permissions unless the user has revoked them.

Permissions are sepparated in normal vs dangerous permissions. For normal permissions, we just need to declare them at the manifest and Android will automatically given them to us. For dangerous permissions, we need the explicit confirmation from the user.

Reference(s)

https://developer.android.com/guide

Read More

Android ViewBinding

ViewBinding is a resource that allows you to write better code that interacts with views. It generates a binding class for each XML layout file.

Usually you’ll use DataBinding. This replaces findViewById() and Butterknife.

ViewBinding Improvements

  • Null Safety since it creates direct reference to views
  • Type Safety the fields in each binding class have types matching the views they reference in the XML file.

Before all, let’s talk about ViewBinding vs DataBinding.

ViewBinding vs DataBinding

With DataBinding we bind data from the code to the views, as well as bind views to the code. If our requirement is just to bind views to the code, ViewBinding is faster and a lot easier to use.

That said, there’s nothing ViewBinding can do that DataBinding cannot do, but we cannot use ViewBinding to bind data from the code to the views and there will be no binding expressions, two way binding or binding adapters.

ViewBinding has two main advantages

  1. It’s easier to use.
  2. It’s faster at compile times.

Implementation

Activate it in the module-level gradle.plugin

android {
    buildFeatures {
        viewBinding = true
    }
}

If you want to ignore a layout file, just add this at root level

<LinearLayout
        tools:viewBindingIgnore="true" >
</LinearLayout>

Usage

The name of the binding follows this pattern result_profile.xml -> ResultProfileBinding.

It will create a field for each element that has an ID. It also includes a getRoot() providing a direct reference for the root view.

in activities

Perform this steps in the activity’s onCreate() method

  1. Call the static inflate() method for the generated binding class. This creates an instance of the binding class for the activity to use.
  2. Get a reference to the root view by calling the getRoot() method.
  3. Pass the root view to setContentView() to make it the active view on the screen.
public class MyActivityExample {

	private ResultProfileBinding binding;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		// point 1
		binding = ResultProfileBinding.inflate(getLayoutInflater());
		// point 2
		View view = binding.getRoot();
		// point 3
		setContentView(view);
	}

}

in fragments

Perform the same steps in the fragment’s onCreateView() method.

public class MyFragmentExample {

	private ResultProfileBinding binding;
	
	@Override
	public View onCreateView(LayoutInflater inflater,
								ViewGroup container,
								Bundle savedInstanceState) {
		binding = ResultProfileBinding.inflate(inflater, container, false);
		return binding.getRoot();
	}

	@Override
	public void onDestroyView() {
		super.onDestroyView();
		binding = null;
	}

}

Resource(s)

https://developer.android.com/topic/libraries/view-binding

Read More

From Java to Android with Kotlin

(Disclaimer: This are my personal notes from following Kotlin and Android courses in Udemy. This is a watered-down version from those courses. Check and buy the original courses if you want to find the full resources I used with more detail)

Android

This are my notes on the progress of things I had to learn to go from Java Developer to develop my first Android App with Android in Kotlin.

ViewBinding
DataBinding
MVVM Architecture
Live Data
ViewModel, LiveData, DataBinding
(wip: I still have to order and clean this series of posts from here on)
Recycler View
Navigation Architecture Component
Android Notifications
Coroutines
WorkManager
Android Testing

Extras:
Dagger2 Framework (dependency injection)
Hilt Framework (Dagger2 wrapper)
Room Framework (SQLite)
Android SQLite experience sheet
Android Development experience

Kotlin

This series of posts explain the main differences in language structures and usage between Kotlin and Java languages. I don’t explain the full Kotlin language, but the novelties that Kotlin implements that may be of interest to a Java developer.

From Java to Kotlin - Data Types & Casting
From Java to Kotlin - Operators & Operators Overloading
From Java to Kotlin - Nullable Types & Null Checks
From Java to Kotlin - Control Flow
From Java to Kotlin - Functions, Varargs & Default Parameters
From Java to Kotlin - Standard Library Functions
From Java to Kotlin - Lambdas
From Java to Kotlin - OOP, Companion Objects & Destructuring in Kotlin
From Java to Kotlin - Exceptions & Collections

Extras:
Kotlin cheat sheet with code examples

Read More