-
Notifications
You must be signed in to change notification settings - Fork 3
Features implementation
Andrea Severi edited this page Dec 8, 2025
·
3 revisions
Hide keys from version control (Git)
The secrets-gradle-plugin is used to hide API keys, urls, etc. from version control. It lets you access these keys inside:
- kotlin: using BuildConfig, like BuildConfig.myKey
- AndroidManifest.xml: with this syntax ${myKey} (
⚠️ keys inside manifest are still visible when decompiling the apk, so make sure to apply restrictions to those APIs)
You can also create buildtype-based or flavor based properties files, like debug.properties for a debug variant.
Encrypted DataStore
- Copy KeyStoreManager.kt
- Change the DataStore serializer, like this:
class UserPreferencesSerializer @Inject constructor(
@Dispatcher(Io) private val ioDispatcher: CoroutineDispatcher,
private val keyStoreManager: KeyStoreManager = KeyStoreManager("user_preferences")
) : Serializer<UserPreferences> {
override val defaultValue: UserPreferences = UserPreferences()
override suspend fun readFrom(input: InputStream): UserPreferences {
// 1st change
val decryptedBytes = keyStoreManager.decrypt(input)
return try {
withContext(ioDispatcher) {
ProtoBuf.decodeFromByteArray<UserPreferences>(bytes = decryptedBytes)
}
} catch (exception: SerializationException) {
throw CorruptionException("Cannot read proto. $exception")
}
}
override suspend fun writeTo(t: UserPreferences, output: OutputStream) {
withContext(ioDispatcher) {
// 2nd change
keyStoreManager.encrypt(
bytes = ProtoBuf.encodeToByteArray(t),
outputStream = output
)
}
}
}Get app info
@Composable
fun MyScreen() {
val context = LocalContext.current
val appVersion = context.getAppVersion()
}
data class AppVersion(
/**
* App string version e.g. 1.0.0, 1.1.0, ...
*/
val versionName: String,
/**
* App code version e.g. 1, 2, 3, ...
*/
val versionCode: Long
)
fun Context.getAppVersion(): AppVersion? {
return try {
val packageManager = this.packageManager
val packageInfo = packageManager.getPackageInfoCompat(this.packageName)
AppVersion(
versionName = packageInfo.versionName,
versionCode = PackageInfoCompat.getLongVersionCode(packageInfo)
)
} catch (e: PackageManager.NameNotFoundException) {
// Timber.e(e)
null
}
}
private fun PackageManager.getPackageInfoCompat(packageName: String): PackageInfo {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
this.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
} else {
this.getPackageInfo(packageName, 0)
}
}Open email app
@Composable
fun MyScreen() {
val context = LocalContext.current
val onSendEmail = { context.sendEmail(Email(to = listOf(EmailAddress("example@gmail.com")))) }
}
fun Context.sendEmail(email: Email) {
val actionSendType = when {
email.attachments.isEmpty() -> Intent.ACTION_SENDTO
email.attachments.size == 1 -> Intent.ACTION_SEND
else -> Intent.ACTION_SEND_MULTIPLE
}
val intent = Intent(actionSendType).apply {
data = Uri.parse("mailto:") // Only email apps handle this.
// to
putExtra(Intent.EXTRA_EMAIL, email.to.map { it.value }.toTypedArray())
// cc
if (email.cc.isNotEmpty()) {
putExtra(Intent.EXTRA_CC, email.cc.map { it.value }.toTypedArray())
}
// bcc
if (email.bcc.isNotEmpty()) {
putExtra(Intent.EXTRA_BCC, email.cc.map { it.value }.toTypedArray())
}
// subject
email.subject?.let {
putExtra(Intent.EXTRA_SUBJECT, it)
}
// body
email.body?.let {
putExtra(Intent.EXTRA_TEXT, it)
}
// attachments
if (email.attachments.isNotEmpty()) {
putExtra(Intent.EXTRA_STREAM, email.attachments.toTypedArray())
}
}
if (intent.resolveActivity(this.packageManager) != null) {
startActivity(intent)
}
}
data class Email(
val to: List<EmailAddress>,
val cc: List<EmailAddress> = emptyList(),
val bcc: List<EmailAddress> = emptyList(),
val subject: String? = null,
val body: String? = null,
val attachments: List<Uri> = emptyList()
)
@JvmInline
value class EmailAddress(val value: String)Get app locale from @Composable
@Composable
fun MyScreen() {
val context = LocalContext.current
val locale = getLocale()
}
@Composable
@ReadOnlyComposable
fun getLocale(): Locale {
val configuration = LocalConfiguration.current
return ConfigurationCompat.getLocales(configuration).get(0)
?: LocaleListCompat.getDefault()[0]!!
}Open PlayStore app
Get util functions from this link (omitted for brevity)
For more complicated features, see here or take a look at other util functions here