Loading repository data…
Loading repository data…
meticha / repository
This repository showcases various design patterns implemented in Jetpack Compose, highlighting their use cases and integration with modern Android development.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
Project maintained by @Cavin
[!NOTE]
This site outlines how to apply these design patterns to the creation and population of composables in a Jetpack Compose or Compose Multiplatform project.
Creational Patterns
Structural Patterns
Behavioral Patterns
A factory design pattern is a generative design pattern that helps to abstract how an object is created. This makes your code more flexible and extensible.
The basic idea of the factory design pattern is to delegate object creation to a factory class. This factory class determines which object is created.
Sample Scenario
Here's a real-world example of the Factory Design Pattern in Jetpack Compose, focusing on a scenario where you want to implement different card layouts for displaying various types of content in a news application:
Scenario: You have a news app where each news item can be displayed in several formats like ' simple' (only text), 'rich' (with image), or 'interactive' (includes interactive elements like a poll).
interface NewsCardFactory {
@Composable
fun CreateCard(newsItem: NewsItem)
}
data class NewsItem(val title: String, val content: String, val imageUrl: String?)
class SimpleCardFactory : NewsCardFactory {
@Composable
override fun CreateCard(newsItem: NewsItem) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
shape = RoundedCornerShape(8.dp)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(newsItem.title, style = MaterialTheme.typography.titleSmall)
Text(newsItem.content, style = MaterialTheme.typography.bodyMedium)
}
}
}
}
class RichCardFactory : NewsCardFactory {
@Composable
override fun CreateCard(newsItem: NewsItem) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
shape = RoundedCornerShape(8.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
newsItem.imageUrl?.let { url ->
AsyncImage(
model = url,
contentDescription = null,
contentScale = ContentScale.FillBounds,
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.clip(RoundedCornerShape(3.dp))
)
}
Text(newsItem.title, style = MaterialTheme.typography.titleSmall)
Text(newsItem.content, style = MaterialTheme.typography.bodyMedium)
}
}
}
}
enum class CardType { SIMPLE, RICH }
@Composable
fun CardFactoryProvider(
cardType: CardType = CardType.SIMPLE,
content: @Composable () -> Unit
) {
val factory = when (cardType) {
CardType.SIMPLE -> SimpleCardFactory()
CardType.RICH -> RichCardFactory()
}
CompositionLocalProvider(LocalCardFactory provides factory) {
content()
}
}
private val LocalCardFactory = staticCompositionLocalOf<NewsCardFactory> {
SimpleCardFactory() // Default
}
@Composable
fun NewsFeed(
newsItems: List<NewsItem>,
modifier: Modifier,
) {
var selectedCardType by remember { mutableStateOf(CardType.SIMPLE) }
CardFactoryProvider(cardType = selectedCardType) { // Or dynamically choose based on item type
LazyColumn(modifier = modifier) {
item {
Row(modifier = Modifier.padding(8.dp)) {
Button(onClick = { selectedCardType = CardType.SIMPLE }) {
Text("Simple Card")
}
Spacer(modifier = Modifier.width(8.dp))
Button(onClick = { selectedCardType = CardType.RICH }) {
Text("Rich Card")
}
}
}
items(newsItems) { item ->
val cardFactory = LocalCardFactory.current
cardFactory.CreateCard(item)
}
}
}
}
This approach allows for a flexible and extensible design where new types of cards can be added by creating new factories without altering existing code that uses these cards. It uses the Factory Pattern to manage the creation of different UI components based on the type of news item, enhancing modularity and maintainability of the UI.
The abstract factory design pattern uses a factory class to create objects from multiple families. This pattern abstracts the object creation process, making your code more readable and flexible.
Sample Scenario
ThemeComponentsFactory (Abstract Factory)
├── LightThemeFactory
└── DarkThemeFactory
├── ThemeButton
│ ├── LightThemeButton
│ └── DarkThemeButton
└── ThemeCard
├── LightThemeCard
└── DarkThemeCard
Define interfaces for themed components:
interface ThemeButton {
@Composable
fun Create(onClick: () -> Unit, content: @Composable () -> Unit)
}
interface ThemeCard {
@Composable
fun Create(content: @Composable () -> Unit)
}
Define the factory interface that creates themed components:
interface ThemeComponentsFactory {
fun createButton(): ThemeButton
fun createCard(): ThemeCard
}
Implement theme-specific factories:
class LightThemeFactory : ThemeComponentsFactory {
override fun createButton(): ThemeButton = LightThemeButton()
override fun createCard(): ThemeCard = LightThemeCard()
}
class DarkThemeFactory : ThemeComponentsFactory {
override fun createButton(): ThemeButton = DarkThemeButton()
override fun createCard(): ThemeCard = DarkThemeCard()
}
@Composable
fun ThemeSwitchingApp() {
var isDarkTheme by remember { mutableStateOf(false) }
val themeFactory: ThemeComponentsFactory = if (isDarkTheme) {
DarkThemeFactory()
} else {
LightThemeFactory()
}
Column(
modifier = Modifier.fillMaxSize().padding(16.dp)
) {
themeFactory.createButton().Create(
onClick = { isDarkTheme = !isDarkTheme }
) {
Text("Switch Theme")
}
themeFactory.createCard().Create {
Text("This is a themed card")
}
}
}
Sample Scenario Imagine we want to manage a global theme configuration for an app, allowing access to the theme state from multiple places without passing it explicitly.
object ThemeConfig {
private var darkModeEnabled: Boolean = false
fun isDarkModeEnabled(): Boolean = darkModeEnabled
fun toggleDarkMode() {
darkModeEnabled = !darkModeEnabled
}
}
@Composable
fun ThemeToggleButton() {
val isDarkMode = remember { mutableStateOf(ThemeConfig.isDarkModeEnabled()) }
Button(onClick = {
ThemeConfig.toggleDarkMode()
isDarkMode.value = ThemeConfig.isDarkModeEnabled()
}) {
Text(if (isDarkMode.value) "Switch to Light Mode" else "Switch to Dark Mode")
}
}
@Composable
fun AppContent() {
val isDarkMode = ThemeConfig.isDarkModeEnabled()
MaterialTheme(colorScheme = if (isDarkMode) darkColors() else lightColors()) {
ThemeToggleButton()
}
}
object keyword inherently implements the singleton pattern.object MySingleton {
fun doSomething() {
println("Singleton Instance")
}
}
lazy delegate to create a singleton only when accessed for the first time.class MySingleton private constructor() {
companion obj