instatic-cms / docs_brain /android.md
Dolor David Prince
feat: InStatic CMS AI pipeline with docs brain
407171a
|
Raw
History Blame Contribute Delete
13.7 kB
# Android Developer Markdown Brain
> Used by AI pipeline to plan, generate, and validate Android apps (Jetpack Compose, XML, SDK APIs).
---
## Project Structure (Compose)
```
app/
├── src/main/
│ ├── AndroidManifest.xml
│ ├── java/com/dolor3v/app/
│ │ ├── MainActivity.kt
│ │ ├── ui/
│ │ │ ├── theme/
│ │ │ │ ├── Color.kt
│ │ │ │ ├── Theme.kt
│ │ │ │ └── Type.kt
│ │ │ ├── screens/
│ │ │ │ ├── HomeScreen.kt
│ │ │ │ └── DetailScreen.kt
│ │ │ └── components/
│ │ ├── data/
│ │ │ ├── repository/
│ │ │ ├── model/
│ │ │ └── api/
│ │ ├── domain/
│ │ │ └── usecase/
│ │ └── di/
│ └── res/
│ ├── drawable/
│ ├── values/
│ │ ├── strings.xml
│ │ ├── colors.xml
│ │ └── themes.xml
│ └── mipmap/
├── build.gradle.kts
└── proguard-rules.pro
build.gradle.kts (project level)
settings.gradle.kts
gradle/libs.versions.toml
```
---
## build.gradle.kts (App Level)
```kotlin
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.hilt)
id("kotlin-kapt")
}
android {
namespace = "com.dolor3v.app"
compileSdk = 35
defaultConfig {
applicationId = "com.dolor3v.app"
minSdk = 26
targetSdk = 35
versionCode = 1
versionName = "1.0.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
signingConfig = signingConfigs.getByName("release")
}
debug {
applicationIdSuffix = ".debug"
isDebuggable = true
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions { jvmTarget = "17" }
buildFeatures {
compose = true
buildConfig = true
}
}
dependencies {
// Core
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
// Compose BOM
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
// Navigation
implementation(libs.androidx.navigation.compose)
// ViewModel
implementation(libs.androidx.lifecycle.viewmodel.compose)
// Hilt DI
implementation(libs.hilt.android)
kapt(libs.hilt.compiler)
implementation(libs.androidx.hilt.navigation.compose)
// Retrofit + OkHttp
implementation(libs.retrofit)
implementation(libs.retrofit.converter.gson)
implementation(libs.okhttp.logging.interceptor)
// Room
implementation(libs.room.runtime)
implementation(libs.room.ktx)
kapt(libs.room.compiler)
// Coil (images)
implementation(libs.coil.compose)
// DataStore
implementation(libs.datastore.preferences)
// Coroutines
implementation(libs.kotlinx.coroutines.android)
// Testing
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
}
```
---
## Jetpack Compose Patterns
### MainActivity
```kotlin
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MyAppTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
AppNavHost()
}
}
}
}
}
```
### Navigation
```kotlin
@Composable
fun AppNavHost(
navController: NavHostController = rememberNavController(),
startDestination: String = Screen.Home.route
) {
NavHost(navController = navController, startDestination = startDestination) {
composable(Screen.Home.route) {
HomeScreen(navController = navController)
}
composable(
route = Screen.Detail.route,
arguments = listOf(navArgument("id") { type = NavType.IntType })
) { backStack ->
val id = backStack.arguments?.getInt("id") ?: 0
DetailScreen(id = id, navController = navController)
}
}
}
sealed class Screen(val route: String) {
object Home : Screen("home")
object Detail : Screen("detail/{id}") {
fun createRoute(id: Int) = "detail/$id"
}
}
```
### ViewModel + StateFlow
```kotlin
@HiltViewModel
class HomeViewModel @Inject constructor(
private val repository: PostRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<UiState<List<Post>>>(UiState.Loading)
val uiState: StateFlow<UiState<List<Post>>> = _uiState.asStateFlow()
init { loadPosts() }
fun loadPosts() {
viewModelScope.launch {
_uiState.value = UiState.Loading
repository.getPosts()
.onSuccess { _uiState.value = UiState.Success(it) }
.onFailure { _uiState.value = UiState.Error(it.message ?: "Unknown error") }
}
}
}
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}
```
### Screen with State Handling
```kotlin
@Composable
fun HomeScreen(
viewModel: HomeViewModel = hiltViewModel(),
navController: NavController
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Scaffold(
topBar = {
TopAppBar(title = { Text("Home") })
}
) { padding ->
Box(modifier = Modifier.padding(padding).fillMaxSize()) {
when (val state = uiState) {
is UiState.Loading -> CircularProgressIndicator(Modifier.align(Alignment.Center))
is UiState.Error -> Text(state.message, color = MaterialTheme.colorScheme.error)
is UiState.Success -> PostList(posts = state.data, onPostClick = { id ->
navController.navigate(Screen.Detail.createRoute(id))
})
}
}
}
}
```
### LazyColumn (RecyclerView equivalent)
```kotlin
@Composable
fun PostList(posts: List<Post>, onPostClick: (Int) -> Unit) {
LazyColumn(
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(posts, key = { it.id }) { post ->
PostCard(post = post, onClick = { onPostClick(post.id) })
}
}
}
@Composable
fun PostCard(post: Post, onClick: () -> Unit) {
Card(
onClick = onClick,
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(post.title, style = MaterialTheme.typography.titleMedium)
Spacer(Modifier.height(4.dp))
Text(post.excerpt, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 2, overflow = TextOverflow.Ellipsis)
}
}
}
```
---
## Room Database
### Entity
```kotlin
@Entity(tableName = "posts")
data class PostEntity(
@PrimaryKey val id: Int,
@ColumnInfo(name = "title") val title: String,
@ColumnInfo(name = "content") val content: String,
@ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis()
)
```
### DAO
```kotlin
@Dao
interface PostDao {
@Query("SELECT * FROM posts ORDER BY created_at DESC")
fun getAllPosts(): Flow<List<PostEntity>>
@Query("SELECT * FROM posts WHERE id = :id")
suspend fun getPostById(id: Int): PostEntity?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertPosts(posts: List<PostEntity>)
@Delete
suspend fun deletePost(post: PostEntity)
@Query("DELETE FROM posts")
suspend fun clearAll()
}
```
### Database
```kotlin
@Database(entities = [PostEntity::class], version = 1, exportSchema = true)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun postDao(): PostDao
companion object {
@Volatile private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase =
INSTANCE ?: synchronized(this) {
Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
.fallbackToDestructiveMigration()
.build().also { INSTANCE = it }
}
}
}
```
---
## Retrofit API Service
```kotlin
interface ApiService {
@GET("posts")
suspend fun getPosts(
@Query("page") page: Int = 1,
@Query("per_page") perPage: Int = 20
): Response<List<PostDto>>
@GET("posts/{id}")
suspend fun getPost(@Path("id") id: Int): Response<PostDto>
@POST("posts")
suspend fun createPost(@Body post: CreatePostDto): Response<PostDto>
@Multipart
@POST("media")
suspend fun uploadMedia(
@Part file: MultipartBody.Part,
@Part("title") title: RequestBody
): Response<MediaDto>
}
// Retrofit instance
@Provides @Singleton
fun provideRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.addInterceptor { chain ->
chain.proceed(
chain.request().newBuilder()
.addHeader("Authorization", "Bearer ${BuildConfig.API_TOKEN}")
.build()
)
}
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
)
.build()
```
---
## AndroidManifest.xml
```xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApp"
android:usesCleartextTraffic="false">
<activity
android:name=".MainActivity"
android:exported="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Deep links -->
<activity android:name=".DeepLinkActivity">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="dolor3v.com" />
</intent-filter>
</activity>
<!-- FCM -->
<service android:name=".service.MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>
```
---
## Material 3 Theme
```kotlin
// Color.kt
val BlueElectric = Color(0xFF3B82F6)
val AmberSpark = Color(0xFFF59E0B)
val DeepSpace = Color(0xFF090E1A)
val SpaceSurface = Color(0xFF0F1629)
private val DarkColorScheme = darkColorScheme(
primary = BlueElectric,
secondary = AmberSpark,
background = DeepSpace,
surface = SpaceSurface,
onPrimary = Color.White,
onBackground = Color(0xFFE2E8F0),
onSurface = Color(0xFF94A3B8),
)
@Composable
fun MyAppTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme
MaterialTheme(colorScheme = colorScheme, typography = Typography, content = content)
}
```