Recipes
Common patterns and solutions for Android applications.Jetpack Compose Integration
Jetpack Compose Integration
A reusable Composable for rendering dynamic strings.
Copy
@Composable
fun StringbootText(
key: String,
modifier: Modifier = Modifier,
style: TextStyle = LocalTextStyle.current,
params: Map<String, Any> = emptyMap()
) {
val text by Stringboot.observe(key, params).collectAsState(initial = "")
Text(
text = text,
modifier = modifier,
style = style
)
}
ViewModel Integration
ViewModel Integration
Fetching strings in a ViewModel for business logic.
Copy
class CheckoutViewModel : ViewModel() {
private val _orderSummary = MutableStateFlow("")
val orderSummary = _orderSummary.asStateFlow()
fun updateSummary(count: Int, total: Double) {
viewModelScope.launch {
val template = Stringboot.getString("order_total_message")
_orderSummary.value = template
.replace("{count}", count.toString())
.replace("{total}", NumberFormat.getCurrencyInstance().format(total))
}
}
}
Language Switcher Logic
Language Switcher Logic
Simple language switching logic.
Copy
fun switchLanguage(context: Context, languageCode: String) {
Stringboot.setLocale(languageCode)
val prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
prefs.edit().putString("language", languageCode).apply()
(context as? Activity)?.recreate()
}
Plug-and-Play Components
Ready-to-use UI components for your app.Language Picker Modal
Language Picker Modal
A Jetpack Compose bottom sheet for selecting languages.
Copy
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LanguagePickerSheet(
onDismiss: () -> Unit,
onLanguageSelected: (String) -> Unit
) {
val languages = remember { Stringboot.getAvailableLanguages() }
val currentLocale = Stringboot.getCurrentLocale()
ModalBottomSheet(onDismissRequest = onDismiss) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Select Language",
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 16.dp)
)
LazyColumn {
items(languages) { lang ->
ListItem(
headlineContent = { Text(lang.name) },
leadingContent = {
if (lang.code == currentLocale) {
Icon(Icons.Default.Check, contentDescription = null)
}
},
modifier = Modifier.clickable {
onLanguageSelected(lang.code)
onDismiss()
}
)
}
}
}
}
}
FAQ Bottom Sheet
FAQ Bottom Sheet
A production-ready FAQ browser with caching, search, and expandable items.1. Data Model2. Data Provider3. UI Component
Copy
data class ContextualFAQUiModel(
val question: String,
val answer: String,
val id: String = java.util.UUID.randomUUID().toString()
)
Copy
object ContextualFAQProvider {
suspend fun getFaqs(
tag: String,
subTags: List<String> = emptyList(),
lang: String = "en"
): List<ContextualFAQUiModel> {
return withContext(Dispatchers.IO) {
try {
val rawFaqs = FAQProvider.getFAQs(
tag = tag,
subTags = subTags,
lang = lang,
allowNetworkFetch = true
)
rawFaqs.map { sdkItem ->
ContextualFAQUiModel(
question = sdkItem.question,
answer = sdkItem.answer
)
}
} catch (e: Exception) {
emptyList()
}
}
}
}
Copy
@Composable
fun ContextualFaqContainer(
tag: String,
title: String = "Help & Support",
onDismiss: () -> Unit
) {
var faqList by remember { mutableStateOf<List<ContextualFAQUiModel>>(emptyList()) }
var isLoading by remember { mutableStateOf(true) }
LaunchedEffect(tag) {
isLoading = true
faqList = ContextualFAQProvider.getFaqs(tag = tag)
isLoading = false
}
if (isLoading) {
Box(Modifier.fillMaxWidth().height(300.dp), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
} else {
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.85f)
.clip(RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp))
.background(Color.White)
) {
// Header
Row(
modifier = Modifier.padding(20.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = title,
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.weight(1f)
)
IconButton(onClick = onDismiss) {
Icon(Icons.Default.Close, contentDescription = "Close")
}
}
// List
LazyColumn(
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(faqList) { item ->
FAQItem(item)
}
}
}
}
}
@Composable
fun FAQItem(item: ContextualFAQUiModel) {
var expanded by remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFFF0F1F5))
.clickable { expanded = !expanded }
.padding(16.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = item.question,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.weight(0.9f)
)
Icon(
imageVector = if (expanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
contentDescription = null
)
}
AnimatedVisibility(visible = expanded) {
Column {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = item.answer,
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}