Here i wanted to create a resuable way to create notifications and notification channels.
In API 33+ you have to create your channel in an Application()
context, but below that level, you can create it wherever.
We pass in the context to have reusable notification models we can use anywhere in our code, so that a notification model isn't just tied down to one activity or service. I want receivers, activities, and services all to receive the same model. The context doesn't really matter anyway.
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.TaskStackBuilder
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat
class NotificationsModelV2(private val CHANNEL_ID: String, private val NOTIFICATION_ID: Int, private val CHANNEL_NAME: String) {
private var channelIsCreated = false
fun createNotification(context: Context, title: String, body: String) {
if (!channelIsCreated) {
createNotificationsChannel(context)
}
// build notification
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle(title)
.setContentText(body)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build()
// send notification
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(NOTIFICATION_ID, notification)
}
fun createNotificationsChannel(context: Context) {
val channel = android.app.NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH
)
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
channelIsCreated = true
}
}
Then here is the object that we use to keep all of our notification models in one place.
object NotificationsGlobalObject {
private var NOTIFICATION_ID = 10
val messageNotificationsModel =
NotificationsModelV2(
"alarm-manager-practice",
getNotificationId(),
"Alarm Manager Practice Notifications"
)
private fun getNotificationId(): Int {
return NOTIFICATION_ID++
}
}
With auto-incrementing notification ids we ensure that we never run into a conflict.
You need to install the GSON dependency to convert strings into JSON.
// install gson
implementation("com.google.code.gson:gson:2.10")
These two classes use OKHttp and generics to provide type-safe requesting of JSON APIs. They are reusable and easy to use. These are the basic steps you'll take to use them:
- Choose which type of data you're going to get back: array or object. Then choose the class you want accordingly
- Provide url and data class that represents fetched data
Here is the class:
package com.example.coffeemasters
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.*
import okhttp3.OkHttpClient
import okhttp3.Request
data class Todo(
val userId: Int,
val id: Int,
val title: String,
val completed: Boolean
)
class APIRequesterObjects<T>(public var url: String, private var TClass: Class<T>) {
private val client = OkHttpClient()
private val request = Request.Builder()
.url(url)
.build()
suspend fun getData(): T? {
val response = withContext(Dispatchers.IO) {
client.newCall(request).execute()
}
val responseData = response.body?.string()
if (responseData == null) {
return null
}
// Deserialize JSON string using Gson
val gson = Gson()
return gson.fromJson(responseData, TClass) as T
}
}
class APIRequesterArrays<T>(
private val url: String,
private val typeToken: TypeToken<List<T>>
) {
private val client = OkHttpClient()
private val request = Request.Builder()
.url(url)
.build()
companion object {
fun <T> createClient(url: String, TClass: Class<T>) : APIRequesterArrays<T> {
val typeToken = object : TypeToken<List<T>>() {}
return APIRequesterArrays<T>(
url = url,
typeToken = typeToken
)
}
}
suspend fun getData(): List<T>? {
val response = withContext(Dispatchers.IO) {
client.newCall(request).execute()
}
val responseData = response.body?.string()
if (responseData == null) {
return null
}
// Deserialize JSON string using Gson
val gson = Gson()
return gson.fromJson(responseData, typeToken.type)
}
}
Then here is how you would use the object version, with steps:
- Create default state to represent fetched data
- Create an instance of the api requester
- Run a
LaunchedEffect {}
, which allows you to use coroutines. Use the IO thread to make the network call, and then use the main thread to update the state. Make sure to usestate.value
with thevalue
property, otherwise it doesn't work for some reason.
@Destination
@Composable
fun SecondScreen(
args: SecondScreenArgs
) {
// 1. instantiate
val apiRequesterTodo = APIRequesterObjects(
url = "https://jsonplaceholder.typicode.com/todos/1",
TClass = Todo::class.java
)
// 2. create default state
var todo = remember {
mutableStateOf(Todo(0, 0, "title", false))
}
// 3. create launched effect that fetches in IO thread,
// then updates UI in main thread
LaunchedEffect(key1 = Unit) {
val todoData = withContext(Dispatchers.IO) {
apiRequesterTodo.getData()
}
todo.value = todoData!!
}
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.White)
.padding(16.dp)
) {
Text(text = "Second Screen message from first screen ${args.someArg}")
Text(text = "Todo: ${todo.value.toString()}")
}
}
To request arrays, it's a bit different:
// 1. create type token and pass in list of data class
val typeToken = object : TypeToken<List<Todo>>() {}
// 2. create instance
val apiRequesterTodo = APIRequesterArrays<Todo>(
url = "https://jsonplaceholder.typicode.com/todos",
typeToken = typeToken
)
// 3. create list state
var todos = remember {
mutableStateListOf<Todo>()
}