Module 4
Module 4
1. AsyncTask
In Android, handling long-running or potentially blocking operations on the main thread (UI
thread) can cause the application to become unresponsive and result in a poor user experience.
To perform these tasks asynchronously, Android provides several mechanisms. However, one
of the older methods, AsyncTask, has been deprecated since Android API level 30 (Android
11).
AsyncTask was used to perform background operations and publish results on the UI thread
without requiring manual thread management. However, it had several limitations and
problems:
• It is not tied to the lifecycle of an Activity or Fragment, which can lead to memory
leaks.
• Difficulties in handling configuration changes like screen rotations.
• Not designed for more complex tasks or those that require parallel execution or task
prioritization.
Coroutine Scope: A lifecycle-bound scope for starting coroutines. Common scopes in Android
include
• viewModelScope
o viewModelScope is an extension property in Android's architecture component
library that provides a coroutine scope tied to the lifecycle of a ViewModel.
o This scope is used to launch coroutines within a ViewModel and ensures that
any coroutines launched are automatically canceled when the ViewModel is
cleared, preventing memory leaks and unnecessary background work.
• lifecycleScope
o lifecycleScope is an extension property provided by Android's Jetpack library
that allows you to launch coroutines tied to the lifecycle of an Activity or
Fragment.
o It helps manage coroutines in a lifecycle-aware way, ensuring that they are
automatically canceled when the lifecycle reaches a certain state
• custom scopes
o User defined scope to launch coroutine.
Suspend Function: A function that can be paused and resumed at a later time. Suspend
functions are at the heart of coroutines and allow them to perform asynchronous operations.
To use coroutines, you need to add the following dependencies to your build.gradle file:
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-
core:1.6.4")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-
android:1.6.4")
Launching Coroutines
import androidx.lifecycle.lifecycleScope
lifecycleScope.launch {
try {
withContext(Dispatchers.IO) {
Thread.sleep(5000L)
//background Tasks
}
// Update the UI with the downloaded data
} catch (e: Exception) {
e.printStackTrace()
}
}
Example Program: This program demonstrates downloading an Image from internet
resource and display using asynchronus background task (coroutine).
// Kotlin Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-
core:1.6.4")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-
android:1.6.4")
<uses-permission android:name="android.permission.INTERNET"/>
Step3: UI Design
<Button
android:id="@+id/downloadButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Download Image1" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:scaleType="centerCrop" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/dollar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter Dollar Value"/>
<Button
android:id="@+id/convertbtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="convert to rupee"/>
<TextView
android:id="@+id/inrview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""/>
</LinearLayout>
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) {
v, insets ->
val systemBars =
insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top,
systemBars.right, systemBars.bottom)
insets
}
val downloadButton: Button =
findViewById(R.id.downloadButton)
imageView = findViewById(R.id.imageView)
progressBar = findViewById(R.id.progressBar)
downloadImage("https://images.pexels.com/photos/3075993/pexels-
photo-3075993.jpeg")
}
convertbtn.setOnClickListener {
if(dollar.text.isNotEmpty()){
var inr = dollar.text.toString().toInt() * 83.2
inrview.text = "INR: $inr"
}
else
Toast.makeText(this,"Enter dollar
value",Toast.LENGTH_SHORT).show()
}
}
This method blocks the calling thread until the image is fully downloaded and decoded, which
is generally not ideal for UI operations or long-running tasks in Android due to potential
performance issues and blocking of the main thread.
• Android apps can send or receive broadcast messages from the Android system and
other Android apps, similar to the publish-subscribe design pattern.
• These broadcasts are sent when an event of interest occurs.
o for example, to notify other apps of something that they might be interested in
(for example, some new data has been downloaded).
• Apps can register to receive specific broadcasts. When a broadcast is sent, the system
automatically routes broadcasts to apps that have subscribed to receive that particular
type of broadcast.
Receiving broadcasts
Broadcast receiver
• android.intent.action.BOOT_COMPLETED
Sent when the device has finished booting.
• android.intent.action.BATTERY_LOW
Broadcast when the battery level is low.
• android.intent.action.ACTION_POWER_CONNECTED
Sent when the device is plugged into power and is charging.
• android.intent.action.ACTION_AIRPLANE_MODE_CHANGED
Broadcast when airplane mode is enabled or disabled.
• android.intent.action.DEVICE_STORAGE_LOW
Sent when the device's available storage is low.
• android.net.wifi.WIFI_STATE_CHANGED
Broadcast when the Wi-Fi state changes (enabled, disabled, enabling, disabling, etc.).
• android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED
Sent when the connection state of the local Bluetooth adapter has changed (e.g.,
connected to or disconnected from a device).
Manifest-declared receivers are broadcast receivers that are registered in the Android
application's manifest file (AndroidManifest.xml). These receivers allow the application to
listen for specific system or app-level broadcasts, such as changes in network connectivity,
battery levels, or system boot completions, even when the app is not running.
Declared in Manifest: These receivers are registered in the manifest file with
an <receiver> tag and associated intent filters.
<application
<!-- other statements -->
<receiver android:name="MyBroadCastReceiver"
android:enabled="true" android:exported="false">
<intent-filter>
<action
android:name="android.intent.action.AIRPLANE_MODE_CHANGED"/>
</intent-filter>
</receiver>
</application>
</manifest>
To receive broadcast from android system, we need to create a class by inheriting from
BroadcastReceiver class
Example Program:
package com.example.broascast
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(myReceiver)
}
}
Once started, a service might continue running for some time, even after the user switches to
another application.
Additionally, a component can bind to a service to interact with it and even perform
interprocess communication (IPC).
For example, a service can handle network transactions, play music, perform file I/O, or
interact with a content provider, all from the background.
Types of Services
Create a Service
To create a service, you must create a subclass of Service. In your implementation, you must
override some callback methods that handle key aspects of the service lifecycle and provide a
mechanism that allows the components to bind to the service, if appropriate.
These are the most important callback methods that you should override:
• onCreate() The system invokes this method to perform one-time setup procedures when
the service is initially created (before it calls either onStartCommand() or onBind()). If
the service is already running, this method is not called.
• onStartCommand() The system invokes this method by calling startService() when
another component (such as an activity) requests that the service be started. When this
method executes, the service is started and can run in the background indefinitely. If
you implement this, it is your responsibility to stop the service when its work is
complete by calling stopSelf() or stopService(). If you only want to provide binding,
you don't need to implement this method.
• onBind() - The system invokes this method by calling bindService() when another
component wants to bind with the service (such as to perform RPC). In your
implementation of this method, you must provide an interface that clients use to
communicate with the service by returning an IBinder. You must always implement this
method; however, if you don't want to allow binding, you should return null.
• onDestroy() - The system invokes this method when the service is no longer used and
is being destroyed. Your service should implement this to clean up any resources such
as threads, registered listeners, or receivers. This is the last call that the service receives.
If a component calls bindService() to create the service and onStartCommand() is not called,
the service runs only as long as the component is bound to it. After the service is unbound from
all of its clients, the system destroys it.
Example:
playbtn.setOnClickListener {
val intent = Intent(this,MusicService::class.java)
startService(intent)
}
stopbtn.setOnClickListener {
val intent = Intent(this,MusicService::class.java)
stopService(intent)
}
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<application
<activity
….
</activity>
</application>
</manifest>
import android.app.Service
import android.content.Intent
import android.media.MediaPlayer
import android.os.IBinder
import android.util.Log
Step 4: Register the MusicService in AndroidManifest.xml using <service> tag after the
<activity> tag
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<application >
….
<activity
android:name=".MainActivity"
android:exported="true">
</activity>
<service android:name=".MusicService" android:enabled="true"
android:exported="false"/>
</application>
</manifest>
<Button
android:id="@+id/playbtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="140dp"
android:text="Play Music"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/stopbtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="68dp"
android:text="Stop Music"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/playbtn" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 5: Implement the kotlin code to start and stop the MusicService in MainActivity.kt
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
A notification is a short message that Android displays outside your app's UI to provide the
user with reminders, communication from other people, or other timely information from your
app.
Users can tap the notification to open your app or take an action directly from the notification.
Appearances on a device
A notification appears as an icon in the status bar, a more detailed entry in the notification
drawer, and a badge on the app's icon.
When you issue a notification, it first appears as an icon in the status bar.
Users can swipe down on the status bar to open the notification drawer, where they can view
more details and take actions with the notification.
A notification remains visible in the notification drawer until it's dismissed by the app or user.
Heads-up notification
Notifications can briefly appear in a floating window called a heads-up notification. This
behavior is normally for important notifications that the user needs to know about immediately,
and it only appears if the device is unlocked.
The heads-up notification appears when your app issues the notification. It disappears after a
moment, but it remains visible in the notification drawer as usual.
Notifications can appear on the lock screen. You can programmatically set whether
notifications posted by your app show on a secure lock screen and, if so, the level of detail
visible.
Users can use the system settings to choose the level of detail visible in lock screen notifications
or to disable all lock screen notifications. Starting with Android 8.0, users can disable or enable
lock screen notifications for each notification channel.
Notification Channels
Starting in Android 8.0 (API level 26), all notifications must be assigned to a channel or they
don't appear. This lets users disable specific notification channels for your app instead of
disabling all your notifications. Users can control the visual and auditory options for each
channel from the Android system settings.
An app can have separate channels for each type of notification the app issues. An app can also
create notification channels in response to choices made by users.
The channel is also where you specify the importance level for your notifications on Android
8.0 and higher, so all notifications posted to the same notification channel have the same
behavior.
Notification Importance Level
Android uses the importance of a notification to determine how much the notification interrupts
the user visually and audibly. The higher the importance of a notification, the more interruptive
the notification is.
On Android 8.0 (API level 26) and higher, the importance of a notification is determined by
the importance of the channel the notification is posted to. Users can change the importance of
a notification channel in the system settings, as shown in figure below.
The possible importance levels and the associated notification behaviors are the following:
Android 13 (API level 33) and higher supports a runtime permission for sending non-exempt
(including Foreground Services (FGS)) notifications from an app: POST_NOTIFICATIONS.
This change helps users focus on the notifications that are most important to them.
The permission that you need to declare in your app's manifest file appears in the following
code snippet:
<manifest>
<uses-permission
android:name="android.permission.POST_NOTIFICATIONS" />
<application
….
</application>
</manifest>
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.POST_NOTIFICATIONS) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),1001)
}
The NotificationManager is retrieved from the system using context.getSystemService. It's cast
to NotificationManager type.
The createNotificationChannel method is called to register the channel with the system. After
this, notifications can be sent through this channel.
Create Notification
A notification in its most basic and compact form—also known as collapsed form—displays
an icon, a title, and a small amount of text content. This section shows how to create a
notification that the user can tap to launch an activity in your app.
fun showNotification(context: Context) {
// Create an intent that will be triggered when the notification
is tapped
val intent = Intent(context, ResponseActivity::class.java)
intent.putExtra("msg","This is the notification content")
val pendingIntent: PendingIntent =
PendingIntent.getActivity(context, 0,
intent,PendingIntent.FLAG_IMMUTABLE )
A PendingIntent is created, which wraps the Intent. This allows the notification to perform the
specified action (open ResponseActivity) when it is tapped.
<manifest>
<uses-permission
android:name="android.permission.POST_NOTIFICATIONS" />
<application
….
</application>
</manifest>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="204dp"
android:text="Send Notification"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 3: Create another Activity called ResponseActivity and include a TextView to show
the full Notification message when user tap on the notification.
<TextView
android:id="@+id/response"
android:layout_width="335dp"
android:layout_height="55dp"
android:layout_marginTop="68dp"
android:ems="10"
android:textColor="#D81B60"
android:textSize="18dp"
android:textAlignment="center"
android:gravity="center_horizontal"
android:inputType="textMultiLine"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
import android.Manifest
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.widget.Button
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
val btn:Button=findViewById(R.id.button)
btn.setOnClickListener {
// Show notification when needed
showNotification(this)
}
}
fun createNotificationChannel(context: Context) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = "MyNotificationChannel"
val descriptionText = "This is my notification channel"
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel("my_channel_1", name, importance)
channel.description = descriptionText
context.getSystemService(Context.NOTIFICATION_SERVICE) as
NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
Step 5: Implement the Kotlin Code to display the full Notification message in another
activity called ResponseActivity when the notification is tapped.
import android.os.Bundle
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity