Skip to content

Commit f61e2c5

Browse files
Create misc module, add broadcast receiver snippets
1 parent d081491 commit f61e2c5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+729
-165
lines changed

misc/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

misc/build.gradle.kts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
plugins {
2+
alias(libs.plugins.android.application)
3+
alias(libs.plugins.kotlin.android)
4+
alias(libs.plugins.ksp)
5+
alias(libs.plugins.hilt)
6+
alias(libs.plugins.compose.compiler)
7+
}
8+
9+
android {
10+
compileSdk = libs.versions.compileSdk.get().toInt()
11+
namespace = "com.example.snippets"
12+
13+
defaultConfig {
14+
applicationId = "com.example.snippets"
15+
minSdk = libs.versions.minSdk.get().toInt()
16+
targetSdk = libs.versions.targetSdk.get().toInt()
17+
versionCode = 1
18+
versionName = "1.0"
19+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20+
}
21+
22+
kotlin {
23+
jvmToolchain(17)
24+
}
25+
26+
buildTypes {
27+
getByName("debug") {
28+
signingConfig = signingConfigs.getByName("debug")
29+
}
30+
31+
getByName("release") {
32+
isMinifyEnabled = false
33+
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"),
34+
"proguard-rules.pro")
35+
}
36+
}
37+
compileOptions {
38+
sourceCompatibility = JavaVersion.VERSION_17
39+
targetCompatibility = JavaVersion.VERSION_17
40+
}
41+
buildFeatures {
42+
compose = true
43+
// Disable unused AGP features
44+
viewBinding = true
45+
}
46+
47+
}
48+
dependencies {
49+
val composeBom = platform(libs.androidx.compose.bom)
50+
implementation(composeBom)
51+
androidTestImplementation(composeBom)
52+
implementation(libs.androidx.core.ktx)
53+
implementation(libs.androidx.activity.compose)
54+
implementation(libs.androidx.compose.runtime)
55+
implementation(libs.androidx.compose.foundation)
56+
implementation(libs.androidx.compose.foundation.layout)
57+
implementation(libs.androidx.compose.ui.util)
58+
implementation(libs.androidx.compose.ui.tooling.preview)
59+
implementation(libs.androidx.compose.material3)
60+
61+
implementation(libs.hilt.android)
62+
implementation(libs.androidx.hilt.navigation.compose)
63+
implementation(libs.kotlinx.serialization.json)
64+
ksp(libs.hilt.compiler)
65+
66+
implementation(libs.androidx.lifecycle.runtime)
67+
testImplementation(libs.junit)
68+
androidTestImplementation(libs.junit)
69+
androidTestImplementation(libs.androidx.test.core)
70+
androidTestImplementation(libs.androidx.test.runner)
71+
androidTestImplementation(libs.androidx.test.espresso.core)
72+
}
File renamed without changes.

misc/src/main/AndroidManifest.xml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
<permission android:name="com.example.snippets.CUSTOM_PERMISSION"/>
4+
5+
<!--[START android_broadcast_receiver_10_manifest_permission]-->
6+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
7+
<!--[END android_broadcast_receiver_10_manifest_permission]-->
8+
9+
<application
10+
android:name=".MyApplication"
11+
android:allowBackup="true"
12+
android:icon="@mipmap/ic_launcher"
13+
android:label="@string/app_name"
14+
android:roundIcon="@mipmap/ic_launcher_round"
15+
android:supportsRtl="true"
16+
android:theme="@style/Theme.Snippets">
17+
18+
<!-- [START android_background_broadcast_1_receiver_manifest]-->
19+
<!-- If this receiver listens for broadcasts sent from the system or from
20+
other apps, even other apps that you own, set android:exported to "true". -->
21+
<receiver android:name=".MyBroadcastReceiver" android:exported="false">
22+
<intent-filter>
23+
<action android:name="com.example.snippets.ACTION_UPDATE_DATA" />
24+
</intent-filter>
25+
</receiver>
26+
<!-- [END android_background_broadcast_1_receiver_manifest]-->
27+
<!-- [START android_background_broadcast_11_receiver_manifest_with_permission]-->
28+
<!-- If this receiver listens for broadcasts sent from the system or from
29+
other apps, even other apps that you own, set android:exported to "true". -->
30+
<receiver
31+
android:name=".MyBroadcastReceiverWithPermission"
32+
android:permission="android.permission.ACCESS_COARSE_LOCATION"
33+
android:exported="true">
34+
<intent-filter>
35+
<action android:name="com.example.snippets.ACTION_UPDATE_DATA" />
36+
</intent-filter>
37+
</receiver>
38+
<!-- [END android_background_broadcast_11_receiver_manifest_with_permission]-->
39+
<activity
40+
android:name="com.example.snippets.MainActivity"
41+
android:exported="true"
42+
android:theme="@style/Theme.Snippets">
43+
<intent-filter>
44+
<action android:name="android.intent.action.MAIN" />
45+
<category android:name="android.intent.category.LAUNCHER" />
46+
</intent-filter>
47+
</activity>
48+
</application>
49+
50+
</manifest>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.example.snippets;
2+
3+
import android.content.BroadcastReceiver;
4+
import android.content.Context;
5+
import android.content.Intent;
6+
import android.util.Log;
7+
8+
import java.util.Objects;
9+
10+
import javax.inject.Inject;
11+
12+
public class BroadcastReceiverJavaSnippets {
13+
14+
// [START android_broadcast_receiver_2_class_java]
15+
public static class MyBroadcastReceiver extends BroadcastReceiver {
16+
@Inject
17+
DataRepository dataRepository;
18+
19+
@Override
20+
public void onReceive(Context context, Intent intent) {
21+
if (Objects.equals(intent.getAction(), "com.example.snippets.ACTION_UPDATE_DATA")) {
22+
String data = intent.getStringExtra("com.example.snippets.DATA");
23+
// Do something with the data, for example send it to a data repository:
24+
if (data != null) { dataRepository.updateData(data); }
25+
}
26+
}
27+
}
28+
// [END android_broadcast_receiver_2_class_java]
29+
}
30+
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package com.example.snippets
2+
3+
import android.content.BroadcastReceiver
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.content.IntentFilter
7+
import androidx.compose.foundation.layout.Arrangement
8+
import androidx.compose.foundation.layout.Column
9+
import androidx.compose.foundation.layout.Row
10+
import androidx.compose.foundation.layout.Spacer
11+
import androidx.compose.foundation.layout.height
12+
import androidx.compose.foundation.layout.padding
13+
import androidx.compose.foundation.layout.widthIn
14+
import androidx.compose.foundation.rememberScrollState
15+
import androidx.compose.foundation.verticalScroll
16+
import androidx.compose.material3.Button
17+
import androidx.compose.material3.Scaffold
18+
import androidx.compose.material3.Text
19+
import androidx.compose.material3.TextField
20+
import androidx.compose.runtime.Composable
21+
import androidx.compose.runtime.getValue
22+
import androidx.compose.runtime.mutableStateOf
23+
import androidx.compose.runtime.remember
24+
import androidx.compose.runtime.setValue
25+
import androidx.compose.ui.Modifier
26+
import androidx.compose.ui.unit.dp
27+
import androidx.core.content.ContextCompat
28+
import androidx.hilt.navigation.compose.hiltViewModel
29+
import androidx.lifecycle.ViewModel
30+
import androidx.lifecycle.compose.LifecycleStartEffect
31+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
32+
import dagger.hilt.android.AndroidEntryPoint
33+
import dagger.hilt.android.lifecycle.HiltViewModel
34+
import dagger.hilt.android.qualifiers.ApplicationContext
35+
import javax.inject.Inject
36+
37+
// Warning for reader: This file contains both the code snippets for apps _sending_ broadcasts, as
38+
// those that are _receiving_ broadcasts. Do not consider this a reference implementation.
39+
//
40+
// The actual sample demonstrates how data can be passed from a broadcast receiver back to the UI,
41+
// through an intermediary data repository.
42+
43+
@AndroidEntryPoint
44+
// [START android_broadcast_receiver_2_class]
45+
class MyBroadcastReceiver : BroadcastReceiver() {
46+
47+
@Inject
48+
lateinit var dataRepository: DataRepository
49+
50+
override fun onReceive(context: Context, intent: Intent) {
51+
if (intent.action == "com.example.snippets.ACTION_UPDATE_DATA") {
52+
val data = intent.getStringExtra("com.example.snippets.DATA") ?: "No data"
53+
// Do something with the data, for example send it to a data repository:
54+
dataRepository.updateData(data)
55+
}
56+
}
57+
}
58+
// [END android_broadcast_receiver_2_class]
59+
60+
@HiltViewModel
61+
class BroadcastReceiverViewModel @Inject constructor(
62+
@ApplicationContext private val context: Context,
63+
dataRepository: DataRepository
64+
) : ViewModel() {
65+
val data = dataRepository.data
66+
67+
@Suppress("MemberVisibilityCanBePrivate")
68+
// [START android_broadcast_receiver_3_definition]
69+
val myBroadcastReceiver = MyBroadcastReceiver()
70+
// [END android_broadcast_receiver_3_definition]
71+
72+
fun registerBroadcastReceiver() {
73+
// [START android_broadcast_receiver_4_intent_filter]
74+
val filter = IntentFilter("com.example.snippets.ACTION_UPDATE_DATA")
75+
// [END android_broadcast_receiver_4_intent_filter]
76+
// [START android_broadcast_receiver_5_exported]
77+
val listenToBroadcastsFromOtherApps = false
78+
val receiverFlags = if (listenToBroadcastsFromOtherApps) {
79+
ContextCompat.RECEIVER_EXPORTED
80+
} else {
81+
ContextCompat.RECEIVER_NOT_EXPORTED
82+
}
83+
// [END android_broadcast_receiver_5_exported]
84+
85+
// [START android_broadcast_receiver_6_register]
86+
ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, receiverFlags)
87+
// [END android_broadcast_receiver_6_register]
88+
89+
// [START android_broadcast_receiver_12_register_with_permission]
90+
ContextCompat.registerReceiver(
91+
context, myBroadcastReceiver, filter,
92+
android.Manifest.permission.ACCESS_COARSE_LOCATION,
93+
null, // scheduler that defines thread, null means run on main thread
94+
receiverFlags
95+
)
96+
// [END android_broadcast_receiver_12_register_with_permission]
97+
}
98+
99+
fun unregisterBroadcastReceiver() {
100+
context.unregisterReceiver(myBroadcastReceiver)
101+
}
102+
103+
fun sendBroadcast(newData: String, usePermission: Boolean = false) {
104+
if(!usePermission) {
105+
// [START android_broadcast_receiver_8_send]
106+
val intent = Intent("com.example.snippets.ACTION_UPDATE_DATA").apply {
107+
putExtra("com.example.snippets.DATA", newData)
108+
setPackage("com.example.snippets")
109+
}
110+
context.sendBroadcast(intent)
111+
// [END android_broadcast_receiver_8_send]
112+
} else {
113+
val intent = Intent("com.example.snippets.ACTION_UPDATE_DATA").apply {
114+
putExtra("com.example.snippets.DATA", newData)
115+
setPackage("com.example.snippets")
116+
}
117+
// [START android_broadcast_receiver_send_9_with_permission]
118+
context.sendBroadcast(intent, android.Manifest.permission.ACCESS_COARSE_LOCATION)
119+
// [END android_broadcast_receiver_9_send_with_permission]
120+
121+
}
122+
}
123+
}
124+
125+
@Composable
126+
fun LifecycleScopedBroadcastReceiver(
127+
registerReceiver: () -> Unit,
128+
unregisterReceiver: () -> Unit
129+
) {
130+
// [START android_broadcast_receiver_7_lifecycle_scoped]
131+
LifecycleStartEffect(registerReceiver, unregisterReceiver) {
132+
registerReceiver()
133+
onStopOrDispose { unregisterReceiver() }
134+
}
135+
// [END android_broadcast_receiver_7_lifecycle_scoped]
136+
}
137+
138+
@Composable
139+
fun BroadcastReceiverSample(
140+
modifier: Modifier = Modifier,
141+
viewModel: BroadcastReceiverViewModel = hiltViewModel()
142+
) {
143+
val data by viewModel.data.collectAsStateWithLifecycle()
144+
BroadcastReceiverSample(
145+
modifier = modifier,
146+
data = data,
147+
registerBroadcastReceiver = viewModel::registerBroadcastReceiver,
148+
unregisterBroadcastReceiver = viewModel::unregisterBroadcastReceiver,
149+
sendBroadcast = viewModel::sendBroadcast
150+
)
151+
}
152+
153+
@Composable
154+
fun BroadcastReceiverSample(
155+
modifier: Modifier = Modifier,
156+
data: String,
157+
registerBroadcastReceiver: () -> Unit,
158+
unregisterBroadcastReceiver: () -> Unit,
159+
sendBroadcast: (newData: String) -> Unit
160+
) {
161+
var newData by remember { mutableStateOf("") }
162+
Scaffold { innerPadding ->
163+
Column(
164+
modifier
165+
.padding(innerPadding)
166+
.padding(16.dp)
167+
) {
168+
Text("Fill in a word, send broadcast, and see it added to the bottom")
169+
Spacer(Modifier.height(16.dp))
170+
TextField(newData, onValueChange = { newData = it }, Modifier.widthIn(min = 160.dp))
171+
Spacer(Modifier.height(16.dp))
172+
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
173+
Button(onClick = { sendBroadcast(newData) }) {
174+
Text("Send broadcast")
175+
}
176+
}
177+
Spacer(Modifier.height(16.dp))
178+
Text(data, Modifier.verticalScroll(rememberScrollState()))
179+
}
180+
}
181+
LifecycleScopedBroadcastReceiver(registerBroadcastReceiver, unregisterBroadcastReceiver)
182+
}
183+
184+
class MyBroadcastReceiverWithPermission : BroadcastReceiver() {
185+
override fun onReceive(context: Context, intent: Intent) {
186+
// no-op, only used to demonstrate manifest registration of receiver with permission
187+
}
188+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.example.snippets
2+
3+
import kotlinx.coroutines.flow.MutableStateFlow
4+
import kotlinx.coroutines.flow.StateFlow
5+
import javax.inject.Inject
6+
import javax.inject.Singleton
7+
8+
@Singleton
9+
class DataRepository @Inject constructor() {
10+
// You would normally save this data in a database or other persistent storage
11+
private val _data: MutableStateFlow<String> = MutableStateFlow("This text will be updated")
12+
val data: StateFlow<String> = _data
13+
14+
fun updateData(data: String) {
15+
_data.value += "\n$data"
16+
}
17+
}

0 commit comments

Comments
 (0)