mobile-best-practices 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -0
- package/assets/data/anti-patterns.csv +114 -0
- package/assets/data/architectures.csv +50 -0
- package/assets/data/code-snippets.csv +80 -0
- package/assets/data/gradle-deps.csv +79 -0
- package/assets/data/libraries.csv +102 -0
- package/assets/data/performance.csv +229 -0
- package/assets/data/platforms/android.csv +247 -0
- package/assets/data/platforms/flutter.csv +55 -0
- package/assets/data/platforms/ios.csv +61 -0
- package/assets/data/platforms/react-native.csv +56 -0
- package/assets/data/project-templates.csv +19 -0
- package/assets/data/reasoning-rules.csv +57 -0
- package/assets/data/security.csv +438 -0
- package/assets/data/testing.csv +74 -0
- package/assets/data/ui-patterns.csv +92 -0
- package/assets/references/CHECKLIST.md +49 -0
- package/assets/references/CODE-RULES.md +123 -0
- package/assets/scripts/__pycache__/core.cpython-314.pyc +0 -0
- package/assets/scripts/core.py +432 -0
- package/assets/scripts/search.py +104 -0
- package/assets/skills/all.md +245 -0
- package/assets/skills/android.md +168 -0
- package/assets/skills/flutter.md +153 -0
- package/assets/skills/ios.md +149 -0
- package/assets/skills/react-native.md +154 -0
- package/assets/templates/base/quick-reference.md +41 -0
- package/assets/templates/base/skill-content.md +60 -0
- package/assets/templates/platforms/agent.json +11 -0
- package/assets/templates/platforms/antigravity.json +13 -0
- package/assets/templates/platforms/claude.json +27 -0
- package/assets/templates/platforms/codebuddy.json +11 -0
- package/assets/templates/platforms/codex.json +11 -0
- package/assets/templates/platforms/continue.json +11 -0
- package/assets/templates/platforms/copilot.json +11 -0
- package/assets/templates/platforms/cursor.json +11 -0
- package/assets/templates/platforms/gemini.json +11 -0
- package/assets/templates/platforms/kiro.json +11 -0
- package/assets/templates/platforms/opencode.json +11 -0
- package/assets/templates/platforms/qoder.json +11 -0
- package/assets/templates/platforms/roocode.json +11 -0
- package/assets/templates/platforms/trae.json +11 -0
- package/assets/templates/platforms/windsurf.json +11 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +94 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +28 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/versions.d.ts +2 -0
- package/dist/commands/versions.d.ts.map +1 -0
- package/dist/commands/versions.js +30 -0
- package/dist/commands/versions.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +23 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +24 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +103 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
ID,Name,Platform,Category,Keywords,Description,Code,Imports,Notes,Reference URL
|
|
2
|
+
viewmodel_basic,Basic ViewModel,Android,Architecture,"viewmodel state flow hilt inject","Standard HiltViewModel with UiState sealed interface","@HiltViewModel\nclass HomeViewModel @Inject constructor(\n private val repository: HomeRepository\n) : ViewModel() {\n\n private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading)\n val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()\n\n init { loadData() }\n\n fun loadData() {\n viewModelScope.launch {\n _uiState.value = HomeUiState.Loading\n try {\n val data = repository.getData()\n _uiState.value = HomeUiState.Success(data)\n } catch (e: Exception) {\n _uiState.value = HomeUiState.Error(e.message ?: ""Unknown error"")\n }\n }\n }\n}\n\nsealed interface HomeUiState {\n data object Loading : HomeUiState\n data class Success(val items: List<Item>) : HomeUiState\n data class Error(val message: String) : HomeUiState\n}","import androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject","Always use sealed interface for UiState. Expose immutable StateFlow.",https://developer.android.com/topic/libraries/architecture/viewmodel
|
|
3
|
+
viewmodel_event,ViewModel with Events,Android,Architecture,"viewmodel event channel side effect navigation snackbar","ViewModel with one-shot events via Channel for navigation/snackbar","@HiltViewModel\nclass LoginViewModel @Inject constructor(\n private val authRepository: AuthRepository\n) : ViewModel() {\n\n private val _uiState = MutableStateFlow(LoginUiState())\n val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()\n\n private val _events = Channel<LoginEvent>(Channel.BUFFERED)\n val events: Flow<LoginEvent> = _events.receiveAsFlow()\n\n fun onLoginClick(email: String, password: String) {\n viewModelScope.launch {\n _uiState.update { it.copy(isLoading = true) }\n val result = authRepository.login(email, password)\n _uiState.update { it.copy(isLoading = false) }\n result.fold(\n onSuccess = { _events.send(LoginEvent.NavigateToHome) },\n onFailure = { _events.send(LoginEvent.ShowError(it.message ?: ""Login failed"")) }\n )\n }\n }\n}\n\ndata class LoginUiState(\n val isLoading: Boolean = false\n)\n\nsealed interface LoginEvent {\n data object NavigateToHome : LoginEvent\n data class ShowError(val message: String) : LoginEvent\n}","import kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.receiveAsFlow\nimport kotlinx.coroutines.flow.update","Use Channel for one-shot events. Never use SharedFlow(replay=0) for events.",https://developer.android.com/topic/architecture/ui-layer/events
|
|
4
|
+
repository_basic,Repository Pattern,Android,Architecture,"repository datasource local remote offline cache","Repository with local cache and remote fallback","class ItemRepository @Inject constructor(\n private val api: ItemApi,\n private val dao: ItemDao,\n @IoDispatcher private val ioDispatcher: CoroutineDispatcher\n) {\n fun getItems(): Flow<List<Item>> = dao.observeAll()\n .map { entities -> entities.map { it.toDomain() } }\n\n suspend fun refreshItems() = withContext(ioDispatcher) {\n val remote = api.getItems()\n dao.upsertAll(remote.map { it.toEntity() })\n }\n\n suspend fun getItem(id: String): Item = withContext(ioDispatcher) {\n dao.getById(id)?.toDomain()\n ?: api.getItem(id).toDomain().also { dao.upsert(it.toEntity()) }\n }\n}","import kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject","Room as single source of truth. Expose Flow from DAO for reactive UI.",https://developer.android.com/topic/architecture/data-layer
|
|
5
|
+
repository_result,Repository with Result,Android,Architecture,"repository result wrapper error handling sealed","Repository returning Result wrapper for explicit error handling","class UserRepository @Inject constructor(\n private val api: UserApi,\n private val dao: UserDao\n) {\n suspend fun getUser(id: String): Result<User> = runCatching {\n val cached = dao.getById(id)\n if (cached != null && !cached.isStale()) {\n return Result.success(cached.toDomain())\n }\n val remote = api.getUser(id)\n dao.upsert(remote.toEntity())\n remote.toDomain()\n }\n\n suspend fun updateProfile(user: User): Result<Unit> = runCatching {\n api.updateUser(user.toDto())\n dao.upsert(user.toEntity())\n }\n}","import javax.inject.Inject","Use runCatching for clean error propagation to ViewModel.",https://developer.android.com/topic/architecture/data-layer
|
|
6
|
+
hilt_module,Hilt DI Module,Android,DI,"hilt module provides singleton binds","Standard Hilt module with @Provides and @Binds","@Module\n@InstallIn(SingletonComponent::class)\nabstract class RepositoryModule {\n @Binds\n abstract fun bindUserRepository(\n impl: UserRepositoryImpl\n ): UserRepository\n}\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject NetworkModule {\n @Provides\n @Singleton\n fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder()\n .addInterceptor(HttpLoggingInterceptor().apply {\n level = if (BuildConfig.DEBUG) BODY else NONE\n })\n .connectTimeout(30, TimeUnit.SECONDS)\n .readTimeout(30, TimeUnit.SECONDS)\n .build()\n\n @Provides\n @Singleton\n fun provideRetrofit(client: OkHttpClient): Retrofit = Retrofit.Builder()\n .baseUrl(BuildConfig.BASE_URL)\n .client(client)\n .addConverterFactory(MoshiConverterFactory.create())\n .build()\n\n @Provides\n @Singleton\n fun provideUserApi(retrofit: Retrofit): UserApi =\n retrofit.create(UserApi::class.java)\n}","import dagger.Binds\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport javax.inject.Singleton","Use @Binds for interface-impl bindings. @Provides for 3rd-party objects.",https://developer.android.com/training/dependency-injection/hilt-android
|
|
7
|
+
hilt_qualifiers,Hilt Dispatchers,Android,DI,"hilt qualifier dispatcher io default main coroutine","Hilt qualifier annotations for Coroutine Dispatchers","@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class IoDispatcher\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class DefaultDispatcher\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class MainDispatcher\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject DispatcherModule {\n @IoDispatcher\n @Provides\n fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO\n\n @DefaultDispatcher\n @Provides\n fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default\n\n @MainDispatcher\n @Provides\n fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main\n}","import dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.Dispatchers\nimport javax.inject.Qualifier","Inject dispatchers for testability. Replace with TestDispatcher in tests.",https://developer.android.com/training/dependency-injection/hilt-android#qualifiers
|
|
8
|
+
room_setup,Room Database Setup,Android,Database,"room database dao entity migration","Complete Room setup with Entity DAO and Database","@Entity(tableName = ""items"")\ndata class ItemEntity(\n @PrimaryKey val id: String,\n val title: String,\n val description: String,\n val imageUrl: String?,\n val createdAt: Long = System.currentTimeMillis()\n)\n\n@Dao\ninterface ItemDao {\n @Query(""SELECT * FROM items ORDER BY createdAt DESC"")\n fun observeAll(): Flow<List<ItemEntity>>\n\n @Query(""SELECT * FROM items WHERE id = :id"")\n suspend fun getById(id: String): ItemEntity?\n\n @Upsert\n suspend fun upsert(item: ItemEntity)\n\n @Upsert\n suspend fun upsertAll(items: List<ItemEntity>)\n\n @Query(""DELETE FROM items"")\n suspend fun deleteAll()\n}\n\n@Database(\n entities = [ItemEntity::class],\n version = 1,\n exportSchema = true\n)\nabstract class AppDatabase : RoomDatabase() {\n abstract fun itemDao(): ItemDao\n}","import androidx.room.*\nimport kotlinx.coroutines.flow.Flow","Use @Upsert over @Insert(onConflict=REPLACE). exportSchema=true for migrations.",https://developer.android.com/training/data-storage/room
|
|
9
|
+
room_hilt,Room Hilt Module,Android,Database,"room hilt module provides database dao","Hilt module providing Room database and DAOs","@Module\n@InstallIn(SingletonComponent::class)\nobject DatabaseModule {\n @Provides\n @Singleton\n fun provideDatabase(\n @ApplicationContext context: Context\n ): AppDatabase = Room.databaseBuilder(\n context,\n AppDatabase::class.java,\n ""app_database""\n )\n .fallbackToDestructiveMigration()\n .build()\n\n @Provides\n fun provideItemDao(db: AppDatabase): ItemDao = db.itemDao()\n}","import android.content.Context\nimport androidx.room.Room\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport dagger.hilt.components.SingletonComponent\nimport javax.inject.Singleton","Use fallbackToDestructiveMigration only in dev. Write proper migrations for prod.",https://developer.android.com/training/data-storage/room
|
|
10
|
+
compose_screen,Compose Screen with ViewModel,Android,Compose,"compose screen viewmodel collectasstate lifecycle","Standard Compose screen consuming ViewModel state","@Composable\nfun HomeScreen(\n viewModel: HomeViewModel = hiltViewModel(),\n onItemClick: (String) -> Unit\n) {\n val uiState by viewModel.uiState.collectAsStateWithLifecycle()\n\n Scaffold(\n topBar = {\n TopAppBar(title = { Text(""Home"") })\n }\n ) { padding ->\n when (val state = uiState) {\n is HomeUiState.Loading -> {\n Box(Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.Center) {\n CircularProgressIndicator()\n }\n }\n is HomeUiState.Success -> {\n LazyColumn(\n modifier = Modifier.padding(padding),\n contentPadding = PaddingValues(16.dp),\n verticalArrangement = Arrangement.spacedBy(8.dp)\n ) {\n items(state.items, key = { it.id }) { item ->\n ItemCard(item = item, onClick = { onItemClick(item.id) })\n }\n }\n }\n is HomeUiState.Error -> {\n ErrorContent(\n message = state.message,\n onRetry = viewModel::loadData,\n modifier = Modifier.padding(padding)\n )\n }\n }\n }\n}","import androidx.compose.runtime.getValue\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle","Always use collectAsStateWithLifecycle. Handle all UiState branches.",https://developer.android.com/develop/ui/compose/state
|
|
11
|
+
compose_card,Material3 Card Component,Android,Compose,"card material3 compose clickable elevation","Reusable Material3 card composable","@Composable\nfun ItemCard(\n item: Item,\n onClick: () -> Unit,\n modifier: Modifier = Modifier\n) {\n ElevatedCard(\n onClick = onClick,\n modifier = modifier.fillMaxWidth()\n ) {\n Row(\n modifier = Modifier.padding(16.dp),\n horizontalArrangement = Arrangement.spacedBy(12.dp),\n verticalAlignment = Alignment.CenterVertically\n ) {\n AsyncImage(\n model = item.imageUrl,\n contentDescription = item.title,\n modifier = Modifier\n .size(64.dp)\n .clip(RoundedCornerShape(8.dp)),\n contentScale = ContentScale.Crop\n )\n Column(modifier = Modifier.weight(1f)) {\n Text(\n text = item.title,\n style = MaterialTheme.typography.titleMedium\n )\n Text(\n text = item.subtitle,\n style = MaterialTheme.typography.bodyMedium,\n color = MaterialTheme.colorScheme.onSurfaceVariant\n )\n }\n }\n }\n}","import androidx.compose.material3.*\nimport coil.compose.AsyncImage","Use ElevatedCard for clickable cards. AsyncImage from Coil for images.",https://developer.android.com/develop/ui/compose/components/card
|
|
12
|
+
compose_pullrefresh,Pull to Refresh,Android,Compose,"pulltorefresh swipe refresh loading indicator","Pull-to-refresh with Material3","@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun RefreshableList(\n items: List<Item>,\n isRefreshing: Boolean,\n onRefresh: () -> Unit,\n modifier: Modifier = Modifier\n) {\n val pullRefreshState = rememberPullToRefreshState()\n\n PullToRefreshBox(\n isRefreshing = isRefreshing,\n onRefresh = onRefresh,\n state = pullRefreshState,\n modifier = modifier\n ) {\n LazyColumn(\n contentPadding = PaddingValues(16.dp),\n verticalArrangement = Arrangement.spacedBy(8.dp)\n ) {\n items(items, key = { it.id }) { item ->\n ItemCard(item = item)\n }\n }\n }\n}","import androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.pulltorefresh.*","Use Material3 PullToRefreshBox. Pass isRefreshing from ViewModel.",https://developer.android.com/reference/kotlin/androidx/compose/material3/pulltorefresh/package-summary
|
|
13
|
+
compose_bottomsheet,Modal Bottom Sheet,Android,Compose,"bottomsheet modal sheet compose material3","Material3 Modal Bottom Sheet","@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun FilterBottomSheet(\n onDismiss: () -> Unit,\n onApply: (Filter) -> Unit\n) {\n val sheetState = rememberModalBottomSheetState()\n\n ModalBottomSheet(\n onDismissRequest = onDismiss,\n sheetState = sheetState\n ) {\n Column(\n modifier = Modifier\n .padding(horizontal = 16.dp)\n .padding(bottom = 32.dp),\n verticalArrangement = Arrangement.spacedBy(16.dp)\n ) {\n Text(\n text = ""Filters"",\n style = MaterialTheme.typography.headlineSmall\n )\n // Filter options here\n Button(\n onClick = { onApply(filter) },\n modifier = Modifier.fillMaxWidth()\n ) {\n Text(""Apply"")\n }\n }\n }\n}","import androidx.compose.material3.*","Add bottom padding for system navigation bar.",https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#ModalBottomSheet
|
|
14
|
+
compose_searchbar,Search Bar,Android,Compose,"searchbar search query compose material3 dockedsearchbar","Material3 SearchBar with suggestions","@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun SearchTopBar(\n query: String,\n onQueryChange: (String) -> Unit,\n onSearch: (String) -> Unit,\n suggestions: List<String>,\n modifier: Modifier = Modifier\n) {\n var expanded by rememberSaveable { mutableStateOf(false) }\n\n SearchBar(\n inputField = {\n SearchBarDefaults.InputField(\n query = query,\n onQueryChange = onQueryChange,\n onSearch = {\n onSearch(it)\n expanded = false\n },\n expanded = expanded,\n onExpandedChange = { expanded = it },\n placeholder = { Text(""Search..."") },\n leadingIcon = { Icon(Icons.Default.Search, ""Search"") },\n trailingIcon = if (query.isNotEmpty()) {\n { IconButton(onClick = { onQueryChange("""") }) { Icon(Icons.Default.Clear, ""Clear"") } }\n } else null\n )\n },\n expanded = expanded,\n onExpandedChange = { expanded = it },\n modifier = modifier\n ) {\n suggestions.forEach { suggestion ->\n ListItem(\n headlineContent = { Text(suggestion) },\n modifier = Modifier.clickable { onSearch(suggestion); expanded = false }\n )\n }\n }\n}","import androidx.compose.material3.*\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.*","Debounce onQueryChange in ViewModel (300ms).",https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#SearchBar
|
|
15
|
+
navigation_setup,Navigation Compose Setup,Android,Navigation,"navigation compose navhost route typesafe","Type-safe Navigation Compose setup with routes","@Serializable data object HomeRoute\n@Serializable data class DetailRoute(val id: String)\n@Serializable data object SettingsRoute\n\n@Composable\nfun AppNavHost(\n navController: NavHostController = rememberNavController()\n) {\n NavHost(\n navController = navController,\n startDestination = HomeRoute\n ) {\n composable<HomeRoute> {\n HomeScreen(\n onItemClick = { id -> navController.navigate(DetailRoute(id)) }\n )\n }\n composable<DetailRoute> { backStackEntry ->\n val route = backStackEntry.toRoute<DetailRoute>()\n DetailScreen(\n itemId = route.id,\n onBack = { navController.popBackStack() }\n )\n }\n composable<SettingsRoute> {\n SettingsScreen(onBack = { navController.popBackStack() })\n }\n }\n}","import androidx.navigation.compose.*\nimport androidx.navigation.toRoute\nimport kotlinx.serialization.Serializable","Use @Serializable data objects for type-safe routes (Navigation 2.8+).",https://developer.android.com/guide/navigation/design/type-safety
|
|
16
|
+
navigation_bottombar,Bottom Navigation with NavHost,Android,Navigation,"bottom navigation bar navhost compose scaffold","Bottom navigation with NavHost and selected state","@Serializable data object HomeRoute\n@Serializable data object SearchRoute\n@Serializable data object ProfileRoute\n\ndata class TopLevelRoute(\n val label: String,\n val route: Any,\n val icon: ImageVector,\n val selectedIcon: ImageVector\n)\n\nval topLevelRoutes = listOf(\n TopLevelRoute(""Home"", HomeRoute, Icons.Outlined.Home, Icons.Filled.Home),\n TopLevelRoute(""Search"", SearchRoute, Icons.Outlined.Search, Icons.Filled.Search),\n TopLevelRoute(""Profile"", ProfileRoute, Icons.Outlined.Person, Icons.Filled.Person)\n)\n\n@Composable\nfun MainScreen() {\n val navController = rememberNavController()\n val navBackStackEntry by navController.currentBackStackEntryAsState()\n\n Scaffold(\n bottomBar = {\n NavigationBar {\n topLevelRoutes.forEach { topRoute ->\n val selected = navBackStackEntry?.destination?.hasRoute(topRoute.route::class) == true\n NavigationBarItem(\n selected = selected,\n onClick = {\n navController.navigate(topRoute.route) {\n popUpTo(navController.graph.findStartDestination().id) { saveState = true }\n launchSingleTop = true\n restoreState = true\n }\n },\n icon = { Icon(if (selected) topRoute.selectedIcon else topRoute.icon, topRoute.label) },\n label = { Text(topRoute.label) }\n )\n }\n }\n }\n ) { padding ->\n NavHost(navController, startDestination = HomeRoute, Modifier.padding(padding)) {\n composable<HomeRoute> { HomeScreen() }\n composable<SearchRoute> { SearchScreen() }\n composable<ProfileRoute> { ProfileScreen() }\n }\n }\n}","import androidx.navigation.compose.*\nimport kotlinx.serialization.Serializable","Save/restore tab state with popUpTo + saveState + restoreState.",https://developer.android.com/develop/ui/compose/components/bottom-navigation
|
|
17
|
+
retrofit_api,Retrofit API Interface,Android,Network,"retrofit api interface suspend get post","Retrofit API interface with suspend functions","interface ItemApi {\n @GET(""items"")\n suspend fun getItems(\n @Query(""page"") page: Int = 1,\n @Query(""limit"") limit: Int = 20\n ): ItemsResponse\n\n @GET(""items/{id}"")\n suspend fun getItem(@Path(""id"") id: String): ItemDto\n\n @POST(""items"")\n suspend fun createItem(@Body item: CreateItemRequest): ItemDto\n\n @PUT(""items/{id}"")\n suspend fun updateItem(\n @Path(""id"") id: String,\n @Body item: UpdateItemRequest\n ): ItemDto\n\n @DELETE(""items/{id}"")\n suspend fun deleteItem(@Path(""id"") id: String)\n\n @Multipart\n @POST(""items/{id}/image"")\n suspend fun uploadImage(\n @Path(""id"") id: String,\n @Part image: MultipartBody.Part\n ): ImageResponse\n}","import retrofit2.http.*\nimport okhttp3.MultipartBody","All functions should be suspend. Use @Query for pagination.",https://square.github.io/retrofit/
|
|
18
|
+
retrofit_auth,Auth Interceptor,Android,Network,"interceptor auth token refresh okhttp header","OkHttp interceptor for auth token injection and refresh","class AuthInterceptor @Inject constructor(\n private val tokenManager: TokenManager\n) : Interceptor {\n override fun intercept(chain: Interceptor.Chain): Response {\n val token = tokenManager.getAccessToken()\n val request = chain.request().newBuilder()\n .apply { token?.let { header(""Authorization"", ""Bearer $it"") } }\n .build()\n return chain.proceed(request)\n }\n}\n\nclass TokenRefreshAuthenticator @Inject constructor(\n private val tokenManager: TokenManager,\n private val authApi: Lazy<AuthApi>\n) : Authenticator {\n override fun authenticate(route: Route?, response: Response): Request? {\n if (response.code != 401) return null\n synchronized(this) {\n val newToken = runBlocking {\n tokenManager.refreshToken(authApi.get())\n } ?: return null\n return response.request.newBuilder()\n .header(""Authorization"", ""Bearer $newToken"")\n .build()\n }\n }\n}","import okhttp3.Interceptor\nimport okhttp3.Response\nimport okhttp3.Authenticator","Use Authenticator for 401 refresh. Lazy inject AuthApi to avoid circular deps.",https://square.github.io/okhttp/features/interceptors/
|
|
19
|
+
paging_setup,Paging3 Setup,Android,List,"paging3 pagingsource remote mediator lazycolumn","Complete Paging3 setup with PagingSource and LazyColumn","class ItemPagingSource(\n private val api: ItemApi\n) : PagingSource<Int, Item>() {\n override fun getRefreshKey(state: PagingState<Int, Item>) =\n state.anchorPosition?.let { state.closestPageToPosition(it)?.prevKey?.plus(1) }\n\n override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> {\n val page = params.key ?: 1\n return try {\n val response = api.getItems(page = page, limit = params.loadSize)\n LoadResult.Page(\n data = response.items.map { it.toDomain() },\n prevKey = if (page == 1) null else page - 1,\n nextKey = if (response.items.isEmpty()) null else page + 1\n )\n } catch (e: Exception) {\n LoadResult.Error(e)\n }\n }\n}\n\n// In ViewModel:\nval items = Pager(\n config = PagingConfig(pageSize = 20, prefetchDistance = 5)\n) { ItemPagingSource(api) }.flow.cachedIn(viewModelScope)\n\n// In Composable:\nval lazyPagingItems = viewModel.items.collectAsLazyPagingItems()\nLazyColumn {\n items(lazyPagingItems.itemCount, key = lazyPagingItems.itemKey { it.id }) { index ->\n lazyPagingItems[index]?.let { ItemCard(it) }\n }\n}","import androidx.paging.*\nimport androidx.paging.compose.collectAsLazyPagingItems\nimport androidx.paging.compose.itemKey","Use cachedIn(viewModelScope). Always provide key for LazyColumn.",https://developer.android.com/topic/libraries/architecture/paging/v3-overview
|
|
20
|
+
datastore_prefs,DataStore Preferences,Android,Storage,"datastore preferences migration sharedpreferences","DataStore Preferences setup with type-safe keys","class UserPreferences @Inject constructor(\n @ApplicationContext private val context: Context\n) {\n private val Context.dataStore by preferencesDataStore(\n name = ""user_preferences""\n )\n\n private object Keys {\n val DARK_MODE = booleanPreferencesKey(""dark_mode"")\n val LANGUAGE = stringPreferencesKey(""language"")\n val ONBOARDING_DONE = booleanPreferencesKey(""onboarding_done"")\n }\n\n val darkMode: Flow<Boolean> = context.dataStore.data\n .map { it[Keys.DARK_MODE] ?: false }\n\n val language: Flow<String> = context.dataStore.data\n .map { it[Keys.LANGUAGE] ?: ""en"" }\n\n suspend fun setDarkMode(enabled: Boolean) {\n context.dataStore.edit { it[Keys.DARK_MODE] = enabled }\n }\n\n suspend fun setLanguage(lang: String) {\n context.dataStore.edit { it[Keys.LANGUAGE] = lang }\n }\n}","import androidx.datastore.preferences.core.*\nimport androidx.datastore.preferences.preferencesDataStore\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.map","Replace SharedPreferences with DataStore. Expose Flow for reactive reads.",https://developer.android.com/topic/libraries/architecture/datastore
|
|
21
|
+
compose_theme,Material3 Theme Setup,Android,Theme,"theme material3 dynamic color typography dark","Complete Material3 theme with dynamic colors","private val DarkColorScheme = darkColorScheme(\n primary = Color(0xFF90CAF9),\n secondary = Color(0xFFCE93D8),\n tertiary = Color(0xFF80CBC4)\n)\n\nprivate val LightColorScheme = lightColorScheme(\n primary = Color(0xFF1976D2),\n secondary = Color(0xFF7B1FA2),\n tertiary = Color(0xFF00897B)\n)\n\n@Composable\nfun AppTheme(\n darkTheme: Boolean = isSystemInDarkTheme(),\n dynamicColor: Boolean = true,\n content: @Composable () -> Unit\n) {\n val colorScheme = when {\n dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {\n val context = LocalContext.current\n if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)\n }\n darkTheme -> DarkColorScheme\n else -> LightColorScheme\n }\n\n MaterialTheme(\n colorScheme = colorScheme,\n typography = AppTypography,\n content = content\n )\n}","import androidx.compose.material3.*\nimport androidx.compose.foundation.isSystemInDarkTheme","Use dynamic colors on Android 12+. Fallback to custom scheme.",https://developer.android.com/develop/ui/compose/designsystems/material3
|
|
22
|
+
compose_empty_error,Empty and Error States,Android,Compose,"empty state error retry illustration placeholder","Reusable empty and error state composables","@Composable\nfun EmptyState(\n title: String,\n description: String,\n icon: ImageVector = Icons.Outlined.Inbox,\n actionLabel: String? = null,\n onAction: (() -> Unit)? = null,\n modifier: Modifier = Modifier\n) {\n Column(\n modifier = modifier\n .fillMaxSize()\n .padding(32.dp),\n horizontalAlignment = Alignment.CenterHorizontally,\n verticalArrangement = Arrangement.Center\n ) {\n Icon(\n imageVector = icon,\n contentDescription = null,\n modifier = Modifier.size(72.dp),\n tint = MaterialTheme.colorScheme.onSurfaceVariant\n )\n Spacer(Modifier.height(16.dp))\n Text(title, style = MaterialTheme.typography.titleLarge)\n Spacer(Modifier.height(8.dp))\n Text(\n description,\n style = MaterialTheme.typography.bodyMedium,\n color = MaterialTheme.colorScheme.onSurfaceVariant,\n textAlign = TextAlign.Center\n )\n if (actionLabel != null && onAction != null) {\n Spacer(Modifier.height(24.dp))\n Button(onClick = onAction) { Text(actionLabel) }\n }\n }\n}\n\n@Composable\nfun ErrorContent(\n message: String,\n onRetry: () -> Unit,\n modifier: Modifier = Modifier\n) {\n EmptyState(\n title = ""Something went wrong"",\n description = message,\n icon = Icons.Outlined.ErrorOutline,\n actionLabel = ""Retry"",\n onAction = onRetry,\n modifier = modifier\n )\n}","import androidx.compose.material3.*\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.outlined.*","Always provide Empty and Error states. Never show blank screen.",
|
|
23
|
+
compose_shimmer,Shimmer Loading Skeleton,Android,Compose,"shimmer loading skeleton placeholder animation","Shimmer loading animation composable","fun Modifier.shimmerEffect(): Modifier = composed {\n var size by remember { mutableStateOf(IntSize.Zero) }\n val transition = rememberInfiniteTransition(label = ""shimmer"")\n val startOffsetX by transition.animateFloat(\n initialValue = -2 * size.width.toFloat(),\n targetValue = 2 * size.width.toFloat(),\n animationSpec = infiniteRepeatable(tween(1000)),\n label = ""shimmer""\n )\n\n background(\n brush = Brush.linearGradient(\n colors = listOf(\n MaterialTheme.colorScheme.surfaceVariant,\n MaterialTheme.colorScheme.surface,\n MaterialTheme.colorScheme.surfaceVariant\n ),\n start = Offset(startOffsetX, 0f),\n end = Offset(startOffsetX + size.width.toFloat(), size.height.toFloat())\n )\n ).onGloballyPositioned { size = it.size }\n}\n\n@Composable\nfun ItemCardShimmer(modifier: Modifier = Modifier) {\n Row(modifier = modifier.padding(16.dp), horizontalArrangement = Arrangement.spacedBy(12.dp)) {\n Box(Modifier.size(64.dp).clip(RoundedCornerShape(8.dp)).shimmerEffect())\n Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(8.dp)) {\n Box(Modifier.fillMaxWidth(0.7f).height(16.dp).clip(RoundedCornerShape(4.dp)).shimmerEffect())\n Box(Modifier.fillMaxWidth(0.5f).height(14.dp).clip(RoundedCornerShape(4.dp)).shimmerEffect())\n }\n }\n}","import androidx.compose.animation.core.*\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.unit.IntSize","Use shimmer over CircularProgressIndicator for content loading.",
|
|
24
|
+
test_viewmodel,ViewModel Unit Test,Android,Testing,"test viewmodel junit turbine mockk coroutine","Complete ViewModel unit test with Turbine and MockK","@OptIn(ExperimentalCoroutinesApi::class)\nclass HomeViewModelTest {\n\n @get:Rule\n val mainDispatcherRule = MainDispatcherRule()\n\n private val repository: HomeRepository = mockk()\n private lateinit var viewModel: HomeViewModel\n\n @Before\n fun setup() {\n coEvery { repository.getData() } returns listOf(Item(""1"", ""Test""))\n viewModel = HomeViewModel(repository)\n }\n\n @Test\n fun `loadData emits Loading then Success`() = runTest {\n viewModel.uiState.test {\n assertEquals(HomeUiState.Loading, awaitItem())\n assertEquals(HomeUiState.Success(listOf(Item(""1"", ""Test""))), awaitItem())\n }\n }\n\n @Test\n fun `loadData emits Error on failure`() = runTest {\n coEvery { repository.getData() } throws IOException(""Network error"")\n viewModel = HomeViewModel(repository)\n\n viewModel.uiState.test {\n assertEquals(HomeUiState.Loading, awaitItem())\n val error = awaitItem()\n assertTrue(error is HomeUiState.Error)\n }\n }\n}\n\nclass MainDispatcherRule(\n private val dispatcher: TestDispatcher = UnconfinedTestDispatcher()\n) : TestWatcher() {\n override fun starting(description: Description) {\n Dispatchers.setMain(dispatcher)\n }\n override fun finished(description: Description) {\n Dispatchers.resetMain()\n }\n}","import app.cash.turbine.test\nimport io.mockk.coEvery\nimport io.mockk.mockk\nimport kotlinx.coroutines.test.*\nimport org.junit.Rule\nimport org.junit.Test","MainDispatcherRule replaces Main dispatcher. Turbine for Flow testing.",https://developer.android.com/training/testing/local-tests
|
|
25
|
+
test_compose,Compose UI Test,Android,Testing,"compose test ui semantics rule click assert","Compose UI test with ComposeTestRule","class HomeScreenTest {\n\n @get:Rule\n val composeTestRule = createComposeRule()\n\n @Test\n fun showsLoadingIndicator_whenLoading() {\n composeTestRule.setContent {\n AppTheme {\n HomeScreen(uiState = HomeUiState.Loading, onItemClick = {})\n }\n }\n composeTestRule.onNode(hasTestTag(""loading_indicator"")).assertIsDisplayed()\n }\n\n @Test\n fun showsItems_whenSuccess() {\n val items = listOf(Item(""1"", ""First""), Item(""2"", ""Second""))\n composeTestRule.setContent {\n AppTheme {\n HomeScreen(uiState = HomeUiState.Success(items), onItemClick = {})\n }\n }\n composeTestRule.onNodeWithText(""First"").assertIsDisplayed()\n composeTestRule.onNodeWithText(""Second"").assertIsDisplayed()\n }\n\n @Test\n fun showsErrorWithRetry_whenError() {\n composeTestRule.setContent {\n AppTheme {\n HomeScreen(uiState = HomeUiState.Error(""Network error""), onItemClick = {})\n }\n }\n composeTestRule.onNodeWithText(""Network error"").assertIsDisplayed()\n composeTestRule.onNodeWithText(""Retry"").assertIsDisplayed()\n }\n}","import androidx.compose.ui.test.*\nimport androidx.compose.ui.test.junit4.createComposeRule\nimport org.junit.Rule\nimport org.junit.Test","Test composables by passing UiState directly. Use testTag for finding nodes.",https://developer.android.com/develop/ui/compose/testing
|
|
26
|
+
app_class,Hilt Application Class,Android,Setup,"hilt application class entry point android","HiltAndroidApp Application class","@HiltAndroidApp\nclass MyApp : Application()\n\n// In AndroidManifest.xml:\n// <application\n// android:name="".MyApp""\n// android:allowBackup=""false""\n// android:theme=""@style/Theme.MyApp""\n// tools:targetApi=""31"">\n// <activity\n// android:name="".MainActivity""\n// android:exported=""true"">\n// <intent-filter>\n// <action android:name=""android.intent.action.MAIN"" />\n// <category android:name=""android.intent.category.LAUNCHER"" />\n// </intent-filter>\n// </activity>\n// </application>","import android.app.Application\nimport dagger.hilt.android.HiltAndroidApp","@HiltAndroidApp is required. Set allowBackup=false for security.",https://developer.android.com/training/dependency-injection/hilt-android
|
|
27
|
+
mainactivity,MainActivity with Compose,Android,Setup,"mainactivity compose setcontent edge edge enableedgetoedge","MainActivity setup with edge-to-edge Compose","@AndroidEntryPoint\nclass MainActivity : ComponentActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n enableEdgeToEdge()\n setContent {\n AppTheme {\n AppNavHost()\n }\n }\n }\n}","import android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.activity.enableEdgeToEdge\nimport dagger.hilt.android.AndroidEntryPoint","enableEdgeToEdge() for modern Android look. @AndroidEntryPoint for Hilt.",https://developer.android.com/develop/ui/compose/setup
|
|
28
|
+
domain_model,Domain Model with Mappers,Android,Architecture,"domain model entity dto mapper clean","Domain model with DTO and Entity mappers","// Domain model\ndata class Item(\n val id: String,\n val title: String,\n val description: String,\n val imageUrl: String?,\n val price: Double,\n val isFavorite: Boolean = false\n)\n\n// DTO (from API)\n@JsonClass(generateAdapter = true)\ndata class ItemDto(\n val id: String,\n val title: String,\n val description: String,\n @Json(name = ""image_url"") val imageUrl: String?,\n val price: Double\n) {\n fun toDomain() = Item(id, title, description, imageUrl, price)\n}\n\n// Entity (Room)\n@Entity(tableName = ""items"")\ndata class ItemEntity(\n @PrimaryKey val id: String,\n val title: String,\n val description: String,\n val imageUrl: String?,\n val price: Double,\n val isFavorite: Boolean = false\n) {\n fun toDomain() = Item(id, title, description, imageUrl, price, isFavorite)\n}\n\nfun Item.toEntity() = ItemEntity(id, title, description, imageUrl, price, isFavorite)","import com.squareup.moshi.Json\nimport com.squareup.moshi.JsonClass\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey","Separate DTO Entity and Domain models. Map at boundaries.",https://developer.android.com/topic/architecture/data-layer
|
|
29
|
+
usecase,UseCase Pattern,Android,Architecture,"usecase invoke operator clean domain business logic","Invocable UseCase with operator fun","abstract class UseCase<in P, R> {\n suspend operator fun invoke(params: P): Result<R> = try {\n Result.success(execute(params))\n } catch (e: Exception) {\n Result.failure(e)\n }\n protected abstract suspend fun execute(params: P): R\n}\n\nclass GetItemUseCase @Inject constructor(\n private val repository: ItemRepository\n) : UseCase<String, Item>() {\n override suspend fun execute(params: String): Item =\n repository.getItem(params)\n}\n\nclass GetItemsUseCase @Inject constructor(\n private val repository: ItemRepository\n) : UseCase<Unit, List<Item>>() {\n override suspend fun execute(params: Unit): List<Item> =\n repository.getItems()\n}\n\n// Usage in ViewModel:\nval result = getItemUseCase(itemId)\nresult.fold(\n onSuccess = { _uiState.value = UiState.Success(it) },\n onFailure = { _uiState.value = UiState.Error(it.message) }\n)","import javax.inject.Inject","Use operator fun invoke for clean call syntax. Only for non-trivial business logic.",https://developer.android.com/topic/architecture/domain-layer
|
|
30
|
+
compose_remember_perf,Remember for Performance,Android,Performance,"compose remember cache expensive calculation sort filter","Optimize expensive calculations with remember","// BAD: Expensive calculation on every recomposition\n@Composable\nfun ContactList(\n contacts: List<Contact>,\n comparator: Comparator<Contact>\n) {\n LazyColumn {\n // PROBLEM: sorts entire list on EVERY recomposition\n items(contacts.sortedWith(comparator)) { contact ->\n ContactCard(contact)\n }\n }\n}\n\n// GOOD: Cache calculation with remember\n@Composable\nfun ContactList(\n contacts: List<Contact>,\n comparator: Comparator<Contact>\n) {\n // Only sorts when contacts or comparator changes\n val sortedContacts = remember(contacts, comparator) {\n contacts.sortedWith(comparator)\n }\n \n LazyColumn {\n items(sortedContacts, key = { it.id }) { contact ->\n ContactCard(contact)\n }\n }\n}","import androidx.compose.runtime.remember\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items","Use remember with keys to cache expensive calculations. Only recalculates when dependencies change.",https://developer.android.com/develop/ui/compose/performance/bestpractices
|
|
31
|
+
compose_derivedstate,DerivedStateOf for Scroll,Android,Performance,"derivedstateof scroll performance recomposition limit","Limit recompositions with derivedStateOf","// BAD: Recomposes on every scroll pixel\n@Composable\nfun ScrollableListWithButton() {\n val listState = rememberLazyListState()\n \n LazyColumn(state = listState) {\n // ...\n }\n \n // PROBLEM: firstVisibleItemIndex changes constantly during scroll\n // causing AnimatedVisibility to recompose on every pixel\n val showButton = listState.firstVisibleItemIndex > 0\n \n AnimatedVisibility(visible = showButton) {\n ScrollToTopButton()\n }\n}\n\n// GOOD: Use derivedStateOf to reduce recomposition\n@Composable\nfun ScrollableListWithButton() {\n val listState = rememberLazyListState()\n \n LazyColumn(state = listState) {\n // ...\n }\n \n // Only recomposes when the boolean value actually changes\n val showButton by remember {\n derivedStateOf {\n listState.firstVisibleItemIndex > 0\n }\n }\n \n AnimatedVisibility(visible = showButton) {\n ScrollToTopButton()\n }\n}","import androidx.compose.runtime.derivedStateOf\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport androidx.compose.foundation.lazy.rememberLazyListState","Use derivedStateOf when derived value changes less frequently than source state.",https://developer.android.com/develop/ui/compose/performance/bestpractices
|
|
32
|
+
compose_lazy_keys,Lazy Layout Stable Keys,Android,Performance,"lazycolumn lazyrow item key stable reorder","Provide stable keys for lazy layouts","// BAD: No keys - full recomposition on reorder\n@Composable\nfun NotesList(notes: List<Note>) {\n LazyColumn {\n // PROBLEM: When a note moves (e.g. sorted by time)\n // Compose thinks all items changed and recomposes everything\n items(notes) { note ->\n NoteRow(note)\n }\n }\n}\n\n// GOOD: Stable keys enable smart recomposition\n@Composable\nfun NotesList(notes: List<Note>) {\n LazyColumn {\n // With key, Compose knows which item is which\n // Only the moved item updates, others skip recomposition\n items(\n items = notes,\n key = { note -> note.id }\n ) { note ->\n NoteRow(note)\n }\n }\n}\n\n// ALSO GOOD: Using itemsIndexed with key\n@Composable\nfun NotesList(notes: List<Note>) {\n LazyColumn {\n itemsIndexed(\n items = notes,\n key = { _, note -> note.id }\n ) { index, note ->\n NoteRow(note, index)\n }\n }\n}","import androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.itemsIndexed","Always provide stable unique keys for list items to optimize recomposition.",https://developer.android.com/develop/ui/compose/performance/bestpractices
|
|
33
|
+
compose_defer_reads,Defer State Reads,Android,Performance,"defer lambda state read recomposition scope offset","Defer state reads to narrow recomposition scope","// BAD: State read in parent causes wide recomposition\n@Composable\nfun SnackDetail() {\n val scroll = rememberScrollState(0)\n \n Box(Modifier.fillMaxSize()) {\n // PROBLEM: Reading scroll.value here means the entire\n // Box recomposes on every scroll change\n Title(snack, scroll.value)\n // ...\n }\n}\n\n@Composable\nfun Title(snack: Snack, scroll: Int) {\n val offset = with(LocalDensity.current) { scroll.toDp() }\n Column(modifier = Modifier.offset(y = offset)) {\n Text(snack.name)\n }\n}\n\n// BETTER: Pass state as lambda to defer read\n@Composable\nfun SnackDetail() {\n val scroll = rememberScrollState(0)\n \n Box(Modifier.fillMaxSize()) {\n // Pass lambda - read happens in Title, not here\n Title(snack, scrollProvider = { scroll.value })\n // ...\n }\n}\n\n@Composable\nfun Title(snack: Snack, scrollProvider: () -> Int) {\n val offset = with(LocalDensity.current) { \n scrollProvider().toDp() \n }\n Column(modifier = Modifier.offset(y = offset)) {\n Text(snack.name)\n }\n}\n\n// BEST: Use Modifier lambda to skip recomposition entirely\n@Composable\nfun SnackDetail() {\n val scroll = rememberScrollState(0)\n \n Box(Modifier.fillMaxSize()) {\n Title(snack, scrollProvider = { scroll.value })\n // ...\n }\n}\n\n@Composable\nfun Title(snack: Snack, scrollProvider: () -> Int) {\n // Layout phase only - no recomposition!\n Column(\n modifier = Modifier.offset {\n IntOffset(x = 0, y = scrollProvider())\n }\n ) {\n Text(snack.name)\n }\n}","import androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.unit.IntOffset\nimport androidx.compose.foundation.rememberScrollState","Pass state as lambdas to defer reads. Use Modifier lambdas for layout-only changes.",https://developer.android.com/develop/ui/compose/performance/bestpractices
|
|
34
|
+
compose_backwards_write,Avoid Backwards Writes,Android,Performance,"backwards write infinite loop state recomposition","Never write to state after reading it","// BAD: Backwards write causes infinite recomposition\n@Composable\nfun BadComposable() {\n var count by remember { mutableStateOf(0) }\n \n // User clicks button\n Button(onClick = { count++ }) {\n Text(""Recompose"")\n }\n \n // Read state\n Text(""Count: $count"")\n \n // PROBLEM: Writing to state AFTER reading it\n // This causes another recomposition, which reads count,\n // then writes again, infinite loop!\n count++ // BACKWARDS WRITE - DON'T DO THIS\n}\n\n// GOOD: Only write state in event handlers\n@Composable\nfun GoodComposable() {\n var count by remember { mutableStateOf(0) }\n \n // State writes ONLY happen in response to events\n Button(onClick = { count++ }) {\n Text(""Increment"")\n }\n \n Button(onClick = { count = 0 }) {\n Text(""Reset"")\n }\n \n // Only read state for display\n Text(""Count: $count"")\n \n // No writes in composition body\n}\n\n// GOOD: Use LaunchedEffect for side effects\n@Composable\nfun TimerComposable() {\n var count by remember { mutableStateOf(0) }\n \n // Side effects in LaunchedEffect, not composition\n LaunchedEffect(Unit) {\n while (true) {\n delay(1000)\n count++\n }\n }\n \n Text(""Seconds: $count"")\n}","import androidx.compose.runtime.*\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.Text\nimport kotlinx.coroutines.delay","Never write to state in composition body after reading it. Use event handlers or LaunchedEffect.",https://developer.android.com/develop/ui/compose/performance/bestpractices
|
|
35
|
+
baseline_profile_gen,Baseline Profile Generation,Android,Startup,"baseline profile macrobenchmark startup navigation","Generate Baseline Profile with Macrobenchmark","// Create benchmark module: benchmark/build.gradle.kts\nplugins {\n id('com.android.test')\n id('androidx.baselineprofile')\n}\n\nandroid {\n targetProjectPath = ':app'\n experimentalProperties['android.experimental.testOptions.managedDevices.emulator.showKernelLogging'] = true\n}\n\ndependencies {\n implementation('androidx.benchmark:benchmark-macro-junit4:1.2.0')\n}\n\n// Benchmark test: StartupBenchmark.kt\n@RunWith(AndroidJUnit4::class)\nclass StartupBenchmark {\n @get:Rule\n val benchmarkRule = MacrobenchmarkRule()\n\n @Test\n fun startup() = benchmarkRule.measureRepeated(\n packageName = 'com.example.app',\n metrics = listOf(StartupTimingMetric()),\n iterations = 5,\n startupMode = StartupMode.COLD,\n setupBlock = {\n pressHome()\n }\n ) {\n startActivityAndWait()\n // Navigate critical paths\n device.findObject(By.text('Profile')).click()\n device.wait(Until.hasObject(By.text('Settings')), 3000)\n }\n}\n\n// In app/build.gradle.kts\nplugins {\n id('androidx.baselineprofile')\n}\n\n// baseline-prof.txt generated in app/src/main","import androidx.benchmark.macro.junit4.MacrobenchmarkRule\nimport androidx.benchmark.macro.StartupMode\nimport androidx.benchmark.macro.StartupTimingMetric","Run ./gradlew :benchmark:pixel6Api33BenchmarkAndroidTest to generate profile. Deploy with release build.",https://developer.android.com/topic/performance/baselineprofiles/overview
|
|
36
|
+
app_startup_initializer,App Startup Initializer,Android,Startup,"app startup initializer dependencies contentprovider","Consolidate initialization with App Startup library","// Create initializers with dependencies\nclass LoggerInitializer : Initializer<Logger> {\n override fun create(context: Context): Logger {\n return Logger.getInstance(context).apply {\n setLogLevel(if (BuildConfig.DEBUG) Log.DEBUG else Log.WARN)\n }\n }\n\n override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()\n}\n\nclass AnalyticsInitializer : Initializer<Analytics> {\n override fun create(context: Context): Analytics {\n return Analytics.initialize(context, apiKey = BuildConfig.ANALYTICS_KEY)\n }\n\n // Analytics depends on Logger\n override fun dependencies() = listOf(LoggerInitializer::class.java)\n}\n\nclass CrashlyticsInitializer : Initializer<Crashlytics> {\n override fun create(context: Context): Crashlytics {\n return Crashlytics.start(context)\n }\n\n // Crashlytics depends on Logger\n override fun dependencies() = listOf(LoggerInitializer::class.java)\n}\n\n// In AndroidManifest.xml - single provider for all\n<provider\n android:name='androidx.startup.InitializationProvider'\n android:authorities='${applicationId}.androidx-startup'\n android:exported='false'\n tools:node='merge'>\n <meta-data\n android:name='com.example.LoggerInitializer'\n android:value='androidx.startup' />\n <meta-data\n android:name='com.example.AnalyticsInitializer'\n android:value='androidx.startup' />\n <meta-data\n android:name='com.example.CrashlyticsInitializer'\n android:value='androidx.startup' />\n</provider>","import androidx.startup.Initializer\nimport android.content.Context","Replaces multiple ContentProviders. Lazy init: AppInitializer.getInstance(context).initializeComponent(AnalyticsInitializer::class.java)",https://developer.android.com/topic/libraries/app-startup
|
|
37
|
+
viewstub_lazy_inflate,ViewStub Lazy Inflation,Android,Startup,"viewstub inflate lazy defer view hierarchy","Defer view inflation with ViewStub","// In layout XML: activity_main.xml\n<LinearLayout xmlns:android='http://schemas.android.com/apk/res/android'\n android:layout_width='match_parent'\n android:layout_height='match_parent'\n android:orientation='vertical'>\n\n <!-- Always visible content -->\n <TextView\n android:id='@+id/title'\n android:layout_width='wrap_content'\n android:layout_height='wrap_content' />\n\n <!-- Deferred complex view -->\n <ViewStub\n android:id='@+id/error_stub'\n android:layout='@layout/error_view'\n android:inflatedId='@+id/error_view'\n android:layout_width='match_parent'\n android:layout_height='wrap_content' />\n\n <!-- Deferred premium feature -->\n <ViewStub\n android:id='@+id/premium_stub'\n android:layout='@layout/premium_features'\n android:layout_width='match_parent'\n android:layout_height='wrap_content' />\n</LinearLayout>\n\n// In Activity/Fragment\nclass MainActivity : AppCompatActivity() {\n private var errorView: View? = null\n\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n setContentView(R.layout.activity_main)\n // ViewStubs not inflated yet - faster startup\n }\n\n private fun showError(message: String) {\n if (errorView == null) {\n val stub = findViewById<ViewStub>(R.id.error_stub)\n errorView = stub.inflate() // Inflate only when needed\n }\n errorView?.findViewById<TextView>(R.id.error_message)?.text = message\n errorView?.visibility = View.VISIBLE\n }\n}","import android.view.ViewStub\nimport android.view.View","Inflate ViewStub only when needed. Check if already inflated to avoid crash.",https://developer.android.com/reference/android/view/ViewStub
|
|
38
|
+
compose_lazy_load,Compose Conditional Loading,Android,Startup,"compose lazy load defer state launchedeffect","Defer expensive composables until needed","// Pattern 1: Immediate conditional rendering\n@Composable\nfun HomeScreen(viewModel: HomeViewModel = hiltViewModel()) {\n val uiState by viewModel.uiState.collectAsStateWithLifecycle()\n var showPremiumDialog by remember { mutableStateOf(false) }\n\n Column {\n // Always rendered\n TopBar()\n ContentList()\n\n // Only compose when needed - similar to ViewStub\n if (showPremiumDialog) {\n PremiumDialog(onDismiss = { showPremiumDialog = false })\n }\n }\n}\n\n// Pattern 2: Deferred loading with LaunchedEffect\n@Composable\nfun DeferredContent() {\n var shouldLoadExpensive by remember { mutableStateOf(false) }\n\n // Load expensive content after initial composition\n LaunchedEffect(Unit) {\n delay(100) // Let first frame render\n shouldLoadExpensive = true\n }\n\n Column {\n // Fast initial content\n Text('Loading...')\n\n // Heavy composable deferred\n if (shouldLoadExpensive) {\n ExpensiveChart()\n ComplexAnimations()\n }\n }\n}\n\n// Pattern 3: Lazy loading on user interaction\n@Composable\nfun ExpandableSection() {\n var isExpanded by remember { mutableStateOf(false) }\n\n Column {\n Button(onClick = { isExpanded = !isExpanded }) {\n Text(if (isExpanded) 'Collapse' else 'Expand')\n }\n\n // Only compose when expanded\n AnimatedVisibility(visible = isExpanded) {\n DetailedContent() // Heavy nested composables\n }\n }\n}","import androidx.compose.runtime.*\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport kotlinx.coroutines.delay","Use state-based conditional rendering to defer expensive composables. Similar to ViewStub concept.",https://developer.android.com/develop/ui/compose/performance
|
|
39
|
+
workmanager_lazy_init,WorkManager Lazy Init,Android,Startup,"workmanager lazy initialization on-demand configuration provider","Defer WorkManager initialization","// Step 1: Disable auto-initialization in AndroidManifest.xml\n<application>\n <provider\n android:name='androidx.startup.InitializationProvider'\n android:authorities='${applicationId}.androidx-startup'\n android:exported='false'\n tools:node='merge'>\n <!-- Remove WorkManager auto-init -->\n <meta-data\n android:name='androidx.work.WorkManagerInitializer'\n android:value='androidx.startup'\n tools:node='remove' />\n </provider>\n</application>\n\n// Step 2: Implement Configuration.Provider in Application\nclass MyApp : Application(), Configuration.Provider {\n override val workManagerConfiguration: Configuration\n get() = Configuration.Builder()\n .setMinimumLoggingLevel(if (BuildConfig.DEBUG) Log.DEBUG else Log.ERROR)\n .setWorkerFactory(DelegatingWorkerFactory().apply {\n addFactory(HiltWorkerFactory())\n })\n .build()\n\n override fun onCreate() {\n super.onCreate()\n // WorkManager NOT initialized here\n }\n}\n\n// Step 3: Initialize on-demand when needed\nclass NotificationRepository @Inject constructor(\n @ApplicationContext private val context: Context\n) {\n fun scheduleNotification() {\n // WorkManager auto-initializes on first getInstance() call\n val workRequest = OneTimeWorkRequestBuilder<NotificationWorker>()\n .setInitialDelay(1, TimeUnit.HOURS)\n .build()\n\n WorkManager.getInstance(context).enqueue(workRequest)\n // Only initialized if user actually schedules work\n }\n}","import androidx.work.Configuration\nimport androidx.work.WorkManager\nimport android.util.Log","Only initialize WorkManager when actually scheduling work. Saves startup time if no work needed.",https://developer.android.com/topic/libraries/architecture/workmanager/advanced/custom-configuration
|
|
40
|
+
vector_drawable_usage,Vector Drawable vs Raster,Android,Startup,"vector drawable scalable webp png optimization","Use vector drawables for scalable icons","// BAD: Using PNG for all densities\nres/\n drawable-mdpi/ic_home.png (3KB)\n drawable-hdpi/ic_home.png (5KB)\n drawable-xhdpi/ic_home.png (8KB)\n drawable-xxhdpi/ic_home.png (12KB)\n drawable-xxxhdpi/ic_home.png (18KB)\n// Total: 46KB for one icon\n\n// GOOD: Single vector drawable\nres/drawable/ic_home.xml\n<vector xmlns:android='http://schemas.android.com/apk/res/android'\n android:width='24dp'\n android:height='24dp'\n android:viewportWidth='24'\n android:viewportHeight='24'\n android:tint='?attr/colorControlNormal'>\n <path\n android:fillColor='@android:color/white'\n android:pathData='M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z'/>\n</vector>\n// Total: <1KB, scales perfectly\n\n// Usage in code\nImageView(context).apply {\n setImageResource(R.drawable.ic_home)\n}\n\n// Compose usage\nIcon(\n imageVector = ImageVector.vectorResource(R.drawable.ic_home),\n contentDescription = 'Home'\n)\n\n// For complex images, use WebP\n// Convert PNG to WebP in Android Studio:\n// Right-click PNG → Convert to WebP\n// Before: app_logo.png (500KB)\n// After: app_logo.webp (45KB)\n// 91% size reduction with minimal quality loss\n\n// Load WebP\nImage(\n painter = painterResource(R.drawable.app_logo), // .webp\n contentDescription = 'Logo'\n)","import androidx.compose.material3.Icon\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.foundation.Image","Use vector drawables for icons. Use WebP for photos. Convert in Android Studio: Right-click → Convert to WebP.",https://developer.android.com/develop/ui/views/graphics/vector-drawable-resources
|
|
41
|
+
android_keystore_keys,Android Keystore Key Generation,Android,Security,"keystore hardware cryptographic aes rsa encryption","Generate and store keys in Android Keystore","// Generate AES key in Keystore\nfun generateAESKey(): SecretKey {\n val keyGenerator = KeyGenerator.getInstance(\n KeyProperties.KEY_ALGORITHM_AES,\n ""AndroidKeyStore""\n )\n \n val keyGenParameterSpec = KeyGenParameterSpec.Builder(\n ""myAESKey"",\n KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT\n )\n .setBlockModes(KeyProperties.BLOCK_MODE_GCM)\n .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)\n .setUserAuthenticationRequired(true)\n .setUserAuthenticationValidityDurationSeconds(30)\n .setRandomizedEncryptionRequired(true)\n .build()\n \n keyGenerator.init(keyGenParameterSpec)\n return keyGenerator.generateKey()\n}\n\n// Generate RSA key pair\nfun generateRSAKeyPair(): KeyPair {\n val keyPairGenerator = KeyPairGenerator.getInstance(\n KeyProperties.KEY_ALGORITHM_RSA,\n ""AndroidKeyStore""\n )\n \n val keyGenParameterSpec = KeyGenParameterSpec.Builder(\n ""myRSAKey"",\n KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY\n )\n .setDigests(KeyProperties.DIGEST_SHA256)\n .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)\n .setKeySize(2048)\n .build()\n \n keyPairGenerator.initialize(keyGenParameterSpec)\n return keyPairGenerator.generateKeyPair()\n}\n\n// Retrieve existing key\nfun getKey(alias: String): Key? {\n val keyStore = KeyStore.getInstance(""AndroidKeyStore"")\n keyStore.load(null)\n return keyStore.getKey(alias, null)\n}","import java.security.KeyPairGenerator\nimport java.security.KeyStore\nimport javax.crypto.KeyGenerator\nimport android.security.keystore.KeyGenParameterSpec\nimport android.security.keystore.KeyProperties","Keys stored in hardware-backed keystore. Use setUserAuthenticationRequired() for biometric-protected keys.",https://developer.android.com/privacy-and-security/keystore
|
|
42
|
+
biometric_auth,BiometricPrompt Authentication,Android,Security,"biometric fingerprint face strong authentication","Implement BIOMETRIC_STRONG authentication","// Set up BiometricPrompt\nclass SecureActivity : AppCompatActivity() {\n private lateinit var biometricPrompt: BiometricPrompt\n private lateinit var promptInfo: BiometricPrompt.PromptInfo\n\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n \n // Create executor\n val executor = ContextCompat.getMainExecutor(this)\n \n // Create callback\n val callback = object : BiometricPrompt.AuthenticationCallback() {\n override fun onAuthenticationSucceeded(\n result: BiometricPrompt.AuthenticationResult\n ) {\n super.onAuthenticationSucceeded(result)\n // User authenticated - proceed with sensitive operation\n proceedWithSecureAction()\n }\n\n override fun onAuthenticationError(\n errorCode: Int,\n errString: CharSequence\n ) {\n super.onAuthenticationError(errorCode, errString)\n Toast.makeText(this@SecureActivity, \n ""Authentication error: $errString"", \n Toast.LENGTH_SHORT).show()\n }\n\n override fun onAuthenticationFailed() {\n super.onAuthenticationFailed()\n Toast.makeText(this@SecureActivity, \n ""Authentication failed"", \n Toast.LENGTH_SHORT).show()\n }\n }\n\n // Initialize BiometricPrompt\n biometricPrompt = BiometricPrompt(this, executor, callback)\n\n // Configure prompt\n promptInfo = BiometricPrompt.PromptInfo.Builder()\n .setTitle(""Authenticate"")\n .setSubtitle(""Verify your identity to proceed"")\n .setAllowedAuthenticators(\n BiometricManager.Authenticators.BIOMETRIC_STRONG or\n BiometricManager.Authenticators.DEVICE_CREDENTIAL\n )\n .build()\n }\n\n fun initiateSecureAction() {\n // Check biometric capability\n val biometricManager = BiometricManager.from(this)\n when (biometricManager.canAuthenticate(\n BiometricManager.Authenticators.BIOMETRIC_STRONG\n )) {\n BiometricManager.BIOMETRIC_SUCCESS ->\n biometricPrompt.authenticate(promptInfo)\n else ->\n Toast.makeText(this, ""Biometric not available"", \n Toast.LENGTH_SHORT).show()\n }\n }\n}","import androidx.biometric.BiometricPrompt\nimport androidx.biometric.BiometricManager\nimport androidx.core.content.ContextCompat","Use BIOMETRIC_STRONG for sensitive operations. Always provide DEVICE_CREDENTIAL fallback.",https://developer.android.com/training/sign-in/biometric-auth
|
|
43
|
+
network_security_config,Network Security Configuration,Android,Security,"network security config certificate pinning cleartext","Configure network security with certificate pinning","<!-- res/xml/network_security_config.xml -->\n<network-security-config>\n <!-- Base config for all connections -->\n <base-config cleartextTrafficPermitted=""false"">\n <trust-anchors>\n <certificates src=""system"" />\n </trust-anchors>\n </base-config>\n\n <!-- Production API with certificate pinning -->\n <domain-config>\n <domain includeSubdomains=""true"">api.production.com</domain>\n <pin-set expiration=""2027-12-31"">\n <!-- Primary certificate SHA-256 hash -->\n <pin digest=""SHA-256"">base64_primary_cert_hash==</pin>\n <!-- Backup certificate for rotation -->\n <pin digest=""SHA-256"">base64_backup_cert_hash==</pin>\n </pin-set>\n </domain-config>\n\n <!-- Staging environment -->\n <domain-config>\n <domain includeSubdomains=""true"">api.staging.com</domain>\n <trust-anchors>\n <certificates src=""@raw/staging_ca"" />\n </trust-anchors>\n </domain-config>\n\n <!-- Debug overrides (only in debug builds) -->\n <debug-overrides>\n <trust-anchors>\n <certificates src=""@raw/debug_ca"" />\n </trust-anchors>\n </debug-overrides>\n</network-security-config>\n\n<!-- AndroidManifest.xml -->\n<application\n android:networkSecurityConfig=""@xml/network_security_config""\n android:usesCleartextTraffic=""false"">\n <!-- ... -->\n</application>\n\n// Get certificate hash for pinning:\n// openssl s_client -servername api.example.com -connect api.example.com:443 | \\\n// openssl x509 -pubkey -noout | \\\n// openssl pkey -pubin -outform der | \\\n// openssl dgst -sha256 -binary | \\\n// openssl enc -base64","N/A - XML configuration","Pin both primary and backup certificates. Set expiration dates. Use debug-overrides for development.",https://developer.android.com/privacy-and-security/security-config
|
|
44
|
+
encrypted_shared_prefs,EncryptedSharedPreferences,Android,Security,"encrypted sharedpreferences androidx security aes","Encrypt SharedPreferences with EncryptedSharedPreferences","// Create encrypted SharedPreferences\nfun createEncryptedPrefs(context: Context): SharedPreferences {\n // Generate or retrieve master key\n val masterKey = MasterKey.Builder(context)\n .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)\n .build()\n\n // Create EncryptedSharedPreferences\n return EncryptedSharedPreferences.create(\n context,\n ""secure_prefs"",\n masterKey,\n EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,\n EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM\n )\n}\n\n// Store sensitive data\nfun storeAuthToken(context: Context, token: String) {\n val encryptedPrefs = createEncryptedPrefs(context)\n encryptedPrefs.edit()\n .putString(""auth_token"", token)\n .apply()\n}\n\n// Retrieve sensitive data\nfun getAuthToken(context: Context): String? {\n val encryptedPrefs = createEncryptedPrefs(context)\n return encryptedPrefs.getString(""auth_token"", null)\n}\n\n// BAD: Plain SharedPreferences (insecure)\nfun insecureStore(context: Context, token: String) {\n val prefs = context.getSharedPreferences(""prefs"", Context.MODE_PRIVATE)\n prefs.edit()\n .putString(""auth_token"", token) // Stored as plaintext!\n .apply()\n}\n\n// Migration from plain to encrypted\nfun migrateToEncrypted(context: Context) {\n val plainPrefs = context.getSharedPreferences(""old_prefs"", Context.MODE_PRIVATE)\n val encryptedPrefs = createEncryptedPrefs(context)\n \n // Copy data\n plainPrefs.all.forEach { (key, value) ->\n when (value) {\n is String -> encryptedPrefs.edit().putString(key, value).apply()\n is Int -> encryptedPrefs.edit().putInt(key, value).apply()\n is Boolean -> encryptedPrefs.edit().putBoolean(key, value).apply()\n // ... other types\n }\n }\n \n // Clear old preferences\n plainPrefs.edit().clear().apply()\n}","import androidx.security.crypto.EncryptedSharedPreferences\nimport androidx.security.crypto.MasterKey\nimport android.content.SharedPreferences","Keys and values encrypted with AES256. Requires androidx.security:security-crypto dependency.",https://developer.android.com/topic/security/data
|
|
45
|
+
play_integrity_check,Play Integrity API Integration,Android,Security,"play integrity api app verification tamper","Integrate Play Integrity API for app verification","// Add dependency: implementation 'com.google.android.play:integrity:1.3.0'\n\n// Request integrity token (client-side)\nclass IntegrityChecker(private val context: Context) {\n private val integrityManager = IntegrityManagerFactory.create(context)\n\n fun checkIntegrity(\n requestHash: String,\n onSuccess: (String) -> Unit,\n onFailure: (Exception) -> Unit\n ) {\n val integrityTokenRequest = IntegrityTokenRequest.builder()\n .setCloudProjectNumber(123456789L) // Your GCP project number\n .setNonce(requestHash)\n .build()\n\n integrityManager\n .requestIntegrityToken(integrityTokenRequest)\n .addOnSuccessListener { response ->\n val token = response.token()\n // Send token to your backend for validation\n onSuccess(token)\n }\n .addOnFailureListener { exception ->\n onFailure(exception)\n }\n }\n}\n\n// Backend validation (Kotlin server-side)\nfun validateIntegrityToken(token: String, projectId: String): Boolean {\n val url = ""https://playintegrity.googleapis.com/v1/"" +\n ""projects/$projectId/apps:checkIntegrity""\n \n val requestBody = mapOf(""integrity_token"" to token)\n \n // Make HTTP POST request with OAuth credentials\n val response = httpClient.post(url, requestBody)\n val verdict = response.verdict\n \n // Check verdict\n return when {\n verdict.deviceIntegrity.deviceRecognitionVerdict.contains(\n ""MEETS_DEVICE_INTEGRITY""\n ) -> {\n // Genuine Google Play device\n true\n }\n verdict.deviceIntegrity.deviceRecognitionVerdict.contains(\n ""MEETS_BASIC_INTEGRITY""\n ) -> {\n // Passes basic integrity checks\n true\n }\n else -> {\n // Suspicious device or tampered app\n false\n }\n }\n}\n\n// Usage example\nfun performSecureAction() {\n val checker = IntegrityChecker(context)\n val requestHash = generateRequestHash() // Your request data hash\n \n checker.checkIntegrity(\n requestHash = requestHash,\n onSuccess = { token ->\n // Send to backend with API call\n apiService.secureEndpoint(token, requestData)\n },\n onFailure = { error ->\n // Handle integrity check failure\n Log.e(""Integrity"", ""Failed: ${error.message}"")\n }\n )\n}","import com.google.android.play.core.integrity.IntegrityManagerFactory\nimport com.google.android.play.core.integrity.IntegrityTokenRequest","Validate tokens server-side. Rate limit requests. Handle PLAY_STORE_VERSION_UNKNOWN gracefully.",https://developer.android.com/google/play/integrity
|
|
46
|
+
explicit_intents,Explicit Intent Security,Android,Security,"explicit intent security pendingintent immutable","Secure Intent usage avoiding hijacking","// GOOD: Explicit intent for intra-app navigation\nfun navigateToProfile(context: Context, userId: String) {\n // Explicit intent - specifies exact component\n val intent = Intent(context, ProfileActivity::class.java).apply {\n putExtra(""user_id"", userId)\n }\n context.startActivity(intent)\n}\n\n// BAD: Implicit intent vulnerable to hijacking\nfun insecureNavigate(context: Context) {\n val intent = Intent(""com.example.ACTION_VIEW_PROFILE"")\n // Any app can register this action and intercept data!\n context.startActivity(intent)\n}\n\n// GOOD: Immutable PendingIntent (Android 12+ requirement)\nfun createNotificationPendingIntent(context: Context): PendingIntent {\n val intent = Intent(context, MainActivity::class.java)\n return PendingIntent.getActivity(\n context,\n 0,\n intent,\n PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT\n )\n}\n\n// GOOD: Validate intent extras in exported components\nclass SecureActivity : AppCompatActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n \n // Validate intent data\n val userId = intent.getStringExtra(""user_id"")\n ?.takeIf { it.matches(Regex(""[0-9]+"")) }\n ?: run {\n finish()\n return\n }\n \n // Use validated data\n loadUserProfile(userId)\n }\n}\n\n// GOOD: Prevent Intent redirection\nfun handleDeepLink(intent: Intent) {\n val uri = intent.data\n \n // Validate URI before using\n if (uri == null || uri.host != ""myapp.com"") {\n return\n }\n \n // NEVER forward untrusted intent extras\n val targetIntent = Intent(this, TargetActivity::class.java)\n // DON'T: targetIntent.putExtras(intent.extras) // DANGEROUS\n // DO: Explicitly copy validated data\n val validatedId = intent.getStringExtra(""id"")\n ?.takeIf { it.matches(Regex(""[a-zA-Z0-9]+"")) }\n validatedId?.let { targetIntent.putExtra(""id"", it) }\n \n startActivity(targetIntent)\n}\n\n// AndroidManifest.xml - secure component export\n// <activity\n// android:name="".SecureActivity""\n// android:exported=""false"" /> <!-- Not accessible externally -->\n//\n// <activity\n// android:name="".PublicActivity""\n// android:exported=""true""\n// android:permission=""com.example.CUSTOM_PERMISSION""> <!-- Protected by permission -->\n// <intent-filter>\n// <action android:name=""android.intent.action.VIEW"" />\n// </intent-filter>\n// </activity>","import android.content.Intent\nimport android.app.PendingIntent","Use explicit intents. Validate all extras. Use FLAG_IMMUTABLE for PendingIntents. Set android:exported='false' by default.",https://developer.android.com/privacy-and-security/risks/intent-redirection
|
|
47
|
+
webview_security_config,WebView Security Configuration,Android,Security,"webview security javascript file access safe browsing","Secure WebView configuration disabling dangerous features","// Secure WebView configuration\nfun configureSecureWebView(webView: WebView) {\n webView.settings.apply {\n // Disable JavaScript unless absolutely required\n javaScriptEnabled = false\n \n // Disable file access to prevent local file inclusion\n allowFileAccess = false\n allowFileAccessFromFileURLs = false\n allowUniversalAccessFromFileURLs = false\n \n // Enable Safe Browsing\n safeBrowsingEnabled = true\n \n // Other security settings\n allowContentAccess = false\n setGeolocationEnabled(false)\n databaseEnabled = false\n domStorageEnabled = false\n }\n \n // Only load HTTPS content\n webView.loadUrl(""https://trusted-domain.com"")\n}\n\n// If JavaScript is required for trusted content\nfun configureWebViewWithJS(webView: WebView) {\n webView.settings.javaScriptEnabled = true\n \n // Use @JavascriptInterface annotation (API 17+)\n class SecureJsBridge {\n @JavascriptInterface\n fun secureMethod(data: String): String {\n // Validate input\n if (!data.matches(Regex(""^[a-zA-Z0-9]+$""))) {\n return ""Invalid input""\n }\n return processSecurely(data)\n }\n }\n \n webView.addJavascriptInterface(SecureJsBridge(), ""SecureBridge"")\n \n // Intercept requests to validate URLs\n webView.webViewClient = object : WebViewClient() {\n override fun shouldOverrideUrlLoading(\n view: WebView,\n request: WebResourceRequest\n ): Boolean {\n val url = request.url.toString()\n // Only allow trusted domains\n if (!url.startsWith(""https://trusted-domain.com"")) {\n return true // Block navigation\n }\n return false // Allow navigation\n }\n }\n}\n\n// BAD: Insecure WebView configuration\nfun insecureWebView(webView: WebView) {\n webView.settings.apply {\n javaScriptEnabled = true // XSS vulnerable\n allowFileAccess = true // File inclusion attacks\n allowFileAccessFromFileURLs = true // CRITICAL vulnerability\n }\n \n // Exposes ALL public methods - DANGEROUS\n webView.addJavascriptInterface(AppAPI(), ""api"")\n \n // Loads any URL including HTTP\n webView.loadUrl(""http://untrusted-site.com"")\n}","import android.webkit.WebView\nimport android.webkit.WebViewClient\nimport android.webkit.JavascriptInterface","Disable JavaScript by default. Block file access. Use @JavascriptInterface. Load only HTTPS. Enable Safe Browsing.",https://developer.android.com/privacy-and-security/risks/webview-unsafe-file-inclusion
|
|
48
|
+
tapjacking_prevention,Tapjacking Prevention with FLAG_SECURE,Android,Security,"tapjacking flag_secure filtertoucheswhenobscured overlay","Prevent overlay attacks and screen recording","// Prevent screen recording and overlays for entire Activity\nclass SecurePaymentActivity : AppCompatActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n \n // FLAG_SECURE prevents:\n // - Screenshots\n // - Screen recording\n // - Display in recent apps\n // - Overlays on some devices\n window.setFlags(\n WindowManager.LayoutParams.FLAG_SECURE,\n WindowManager.LayoutParams.FLAG_SECURE\n )\n \n setContentView(R.layout.activity_payment)\n }\n}\n\n// Apply FLAG_SECURE to all activities globally\nclass SecureApplication : Application() {\n override fun onCreate() {\n super.onCreate()\n \n registerActivityLifecycleCallbacks(\n object : ActivityLifecycleCallbacks {\n override fun onActivityCreated(\n activity: Activity,\n bundle: Bundle?\n ) {\n activity.window.setFlags(\n WindowManager.LayoutParams.FLAG_SECURE,\n WindowManager.LayoutParams.FLAG_SECURE\n )\n }\n // ... other callbacks\n }\n )\n }\n}\n\n// Protect specific views with filterTouchesWhenObscured\nclass SecurityUtils {\n fun setupSecureButton(button: Button) {\n // Ignore touches when view is obscured by overlay\n button.filterTouchesWhenObscured = true\n \n button.setOnClickListener {\n if (isOverlayDetected()) {\n showOverlayWarning()\n return@setOnClickListener\n }\n performSecureAction()\n }\n }\n \n // Detect if overlay permission is granted to suspicious apps\n fun isOverlayDetected(): Boolean {\n if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n return Settings.canDrawOverlays(context)\n }\n return false\n }\n}\n\n// XML approach\n// <Button\n// android:id=""@+id/confirm_payment_button""\n// android:filterTouchesWhenObscured=""true""\n// android:text=""Confirm Payment"" />\n\n// Check for overlay before sensitive operations\nfun performPayment() {\n if (Settings.canDrawOverlays(context)) {\n AlertDialog.Builder(this)\n .setTitle(""Security Warning"")\n .setMessage(""An overlay is detected. Please disable screen overlay permissions."")\n .setPositiveButton(""Settings"") { _, _ ->\n startActivity(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION))\n }\n .show()\n return\n }\n \n // Proceed with payment\n processPayment()\n}","import android.view.WindowManager\nimport android.provider.Settings\nimport android.widget.Button","Use FLAG_SECURE for sensitive screens. Set filterTouchesWhenObscured on critical buttons. Check for overlay permissions.",https://developer.android.com/privacy-and-security/risks/tapjacking
|
|
49
|
+
secure_logging,Secure Logging with BuildConfig and R8,Android,Security,"logging buildconfig debug r8 strip production","Prevent sensitive data logging in production","// GOOD: Conditional logging with BuildConfig.DEBUG\nobject SecureLogger {\n private const val TAG = ""MyApp""\n \n fun debug(message: String) {\n if (BuildConfig.DEBUG) {\n Log.d(TAG, message)\n }\n }\n \n fun info(message: String) {\n if (BuildConfig.DEBUG) {\n Log.i(TAG, message)\n }\n }\n \n // Always log errors, but sanitize data\n fun error(message: String, throwable: Throwable? = null) {\n val sanitizedMessage = sanitize(message)\n Log.e(TAG, sanitizedMessage, throwable)\n }\n \n private fun sanitize(message: String): String {\n // Remove sensitive patterns\n return message\n .replace(Regex(""password=\\\\S+""), ""password=***"")\n .replace(Regex(""token=\\\\S+""), ""token=***"")\n .replace(Regex(""api[_-]?key=\\\\S+"", RegexOption.IGNORE_CASE), ""api_key=***"")\n .replace(Regex(""\\\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\\.[A-Z|a-z]{2,}\\\\b""), ""***@***.***"")\n }\n}\n\n// R8/ProGuard configuration (proguard-rules.pro)\n// Strip all debug and info logs in release builds\n// -assumenosideeffects class android.util.Log {\n// public static *** d(...);\n// public static *** v(...);\n// public static *** i(...);\n// }\n//\n// Keep only warnings and errors\n// -assumenosideeffects class * {\n// void debug*(...);\n// void verbose*(...);\n// }\n\n// Gradle configuration (build.gradle.kts)\n// android {\n// buildTypes {\n// release {\n// isMinifyEnabled = true\n// proguardFiles(\n// getDefaultProguardFile(""proguard-android-optimize.txt""),\n// ""proguard-rules.pro""\n// )\n// }\n// }\n// }\n\n// Usage\nclass UserRepository {\n fun login(username: String, password: String) {\n // GOOD: No password in logs\n SecureLogger.debug(""Login attempt for user: $username"")\n \n // BAD: Logs password!\n // Log.d(""Auth"", ""Login: $username with password: $password"")\n \n try {\n performLogin(username, password)\n } catch (e: Exception) {\n // Sanitized error logging\n SecureLogger.error(""Login failed"", e)\n }\n }\n}\n\n// Production logging to internal storage (for error tracking)\nclass ProductionLogger(private val context: Context) {\n private val logFile = File(context.filesDir, ""app_logs.txt"")\n \n fun logProduction(level: String, message: String) {\n if (!BuildConfig.DEBUG) {\n val timestamp = SimpleDateFormat(""yyyy-MM-dd HH:mm:ss"", Locale.US)\n .format(Date())\n val sanitizedMessage = SecureLogger.sanitize(message)\n val entry = ""$timestamp [$level] $sanitizedMessage\\n""\n \n logFile.appendText(entry)\n \n // Rotate logs if too large\n if (logFile.length() > 1_000_000) {\n rotateLogs()\n }\n }\n }\n}","import android.util.Log\nimport java.io.File","Wrap logs in BuildConfig.DEBUG. Configure R8 to strip debug logs. Sanitize all production logs. Never log PII.",https://developer.android.com/privacy-and-security/risks/log-info-disclosure
|
|
50
|
+
sql_injection_prevention,SQL Injection Prevention with Room,Android,Security,"sql injection room parameterized query rawquery","Prevent SQL injection with parameterized queries","// GOOD: Room with parameterized queries (recommended)\n@Dao\ninterface UserDao {\n // Compile-time verified, SQL injection safe\n @Query(""SELECT * FROM users WHERE email = :email"")\n suspend fun findByEmail(email: String): User?\n \n @Query(""SELECT * FROM users WHERE email = :email AND active = :active"")\n suspend fun findActiveUser(email: String, active: Boolean): User?\n \n // Multiple parameters\n @Query(""""""\n SELECT * FROM users \n WHERE department = :dept \n AND salary > :minSalary \n AND hire_date >= :startDate\n """""")\n suspend fun findUsers(\n dept: String,\n minSalary: Int,\n startDate: Long\n ): List<User>\n \n // LIKE queries - still safe\n @Query(""SELECT * FROM users WHERE name LIKE '%' || :searchTerm || '%'"")\n suspend fun searchUsers(searchTerm: String): List<User>\n}\n\n// GOOD: SQLiteDatabase with parameterized queries\nclass UserRepository(private val db: SQLiteDatabase) {\n fun findUser(email: String): User? {\n // Use selectionArgs for parameterization\n val cursor = db.query(\n ""users"", // table\n null, // columns (null = all)\n ""email = ? AND active = ?"", // selection with placeholders\n arrayOf(email, ""1""), // selectionArgs\n null, // groupBy\n null, // having\n null // orderBy\n )\n \n return cursor.use {\n if (it.moveToFirst()) parseUser(it) else null\n }\n }\n \n // Dynamic WHERE clause - still safe\n fun findUsersDynamic(filters: Map<String, String>): List<User> {\n val whereClause = filters.keys.joinToString("" AND "") { ""$it = ?"" }\n val whereArgs = filters.values.toTypedArray()\n \n val cursor = db.query(\n ""users"",\n null,\n whereClause,\n whereArgs,\n null, null, null\n )\n \n return cursor.use { parseCursor(it) }\n }\n}\n\n// Input validation before queries\nfun validateAndQuery(userInput: String): User? {\n // Validate input format\n val safeEmail = userInput.takeIf { \n it.matches(Regex(""^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$""))\n } ?: throw IllegalArgumentException(""Invalid email format"")\n \n // Now safe to use in query\n return userDao.findByEmail(safeEmail)\n}\n\n// BAD: SQL injection vulnerable code\nfun insecureQuery(userInput: String): User? {\n // DANGEROUS: String concatenation\n val query = ""SELECT * FROM users WHERE email = '$userInput'""\n val cursor = db.rawQuery(query, null)\n // Attacker can input: admin@example.com' OR '1'='1\n \n return cursor.use { if (it.moveToFirst()) parseUser(it) else null }\n}\n\n// BAD: rawQuery with concatenation\nfun deleteUser(userId: String) {\n // DANGEROUS\n db.execSQL(""DELETE FROM users WHERE id = $userId"")\n // Attacker can input: 1 OR 1=1 (deletes all users!)\n}\n\n// GOOD: ContentProvider with URI validation\nclass SecureContentProvider : ContentProvider() {\n override fun query(\n uri: Uri,\n projection: Array<String>?,\n selection: String?,\n selectionArgs: Array<String>?,\n sortOrder: String?\n ): Cursor? {\n // Validate URI\n val userId = uri.lastPathSegment?.toLongOrNull()\n ?: throw IllegalArgumentException(""Invalid user ID"")\n \n // Use parameterized query\n return db.query(\n ""users"",\n projection,\n ""id = ?"",\n arrayOf(userId.toString()),\n null, null, sortOrder\n )\n }\n}","import androidx.room.*\nimport android.database.sqlite.SQLiteDatabase\nimport android.content.ContentProvider","Always use Room @Query or SQLiteDatabase query() with selectionArgs. Never concatenate user input into SQL. Validate inputs.",https://developer.android.com/training/data-storage/room/accessing-data
|
|
51
|
+
backup_rules_config,Backup Security Configuration,Android,Security,"backup allowbackup fullbackupcontent exclude sensitive","Control what data is backed up to prevent leaks","<!-- AndroidManifest.xml - Option 1: Disable backup completely -->\n<application\n android:allowBackup=""false""\n android:name="".MyApplication"">\n <!-- Highest security: Nothing is backed up -->\n</application>\n\n<!-- AndroidManifest.xml - Option 2: Selective backup with rules -->\n<application\n android:allowBackup=""true""\n android:fullBackupContent=""@xml/backup_rules""\n android:dataExtractionRules=""@xml/data_extraction_rules"">\n <!-- Android 12+: Use dataExtractionRules -->\n <!-- Android 11-: Use fullBackupContent -->\n</application>\n\n<!-- res/xml/backup_rules.xml (Android 6-11) -->\n<full-backup-content>\n <!-- Exclude sensitive databases -->\n <exclude domain=""database"" path=""sensitive_data.db""/>\n <exclude domain=""database"" path=""user_credentials.db""/>\n \n <!-- Exclude encrypted preferences -->\n <exclude domain=""sharedpref"" path=""auth_prefs.xml""/>\n <exclude domain=""sharedpref"" path=""encrypted_prefs.xml""/>\n \n <!-- Exclude all files in keys directory -->\n <exclude domain=""file"" path=""keys/""/>\n <exclude domain=""file"" path=""certificates/""/>\n \n <!-- Exclude cache -->\n <exclude domain=""file"" path=""cache/""/>\n \n <!-- Include specific safe data -->\n <include domain=""sharedpref"" path=""app_settings.xml""/>\n <include domain=""database"" path=""app_content.db""/>\n</full-backup-content>\n\n<!-- res/xml/data_extraction_rules.xml (Android 12+) -->\n<data-extraction-rules>\n <cloud-backup>\n <!-- Rules for cloud backup (Google Drive) -->\n <exclude domain=""sharedpref"" path=""auth_prefs.xml""/>\n <exclude domain=""database"" path=""sensitive_data.db""/>\n <exclude domain=""file"" path=""keys/""/>\n </cloud-backup>\n \n <device-transfer>\n <!-- Rules for device-to-device transfer -->\n <exclude domain=""sharedpref"" path=""device_specific_prefs.xml""/>\n <exclude domain=""file"" path=""temp/""/>\n </device-transfer>\n</data-extraction-rules>\n\n// Kotlin code: Programmatically exclude from recents\nclass SensitiveActivity : AppCompatActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n \n // Exclude from recent apps (screenshot visible in task switcher)\n // Deprecated in API 21+ but still useful\n window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)\n }\n}\n\n// AndroidManifest.xml: Exclude activity from recents\n// <activity\n// android:name="".SensitiveActivity""\n// android:excludeFromRecents=""true""\n// android:noHistory=""true"" />\n\n// Verify backup settings at runtime\nclass BackupChecker(private val context: Context) {\n fun isBackupEnabled(): Boolean {\n val packageManager = context.packageManager\n val applicationInfo = packageManager.getApplicationInfo(\n context.packageName,\n PackageManager.GET_META_DATA\n )\n \n return (applicationInfo.flags and ApplicationInfo.FLAG_ALLOW_BACKUP) != 0\n }\n \n fun logBackupStatus() {\n if (BuildConfig.DEBUG) {\n Log.d(""Backup"", ""Backup enabled: ${isBackupEnabled()}"")\n }\n }\n}\n\n// Custom BackupAgent for advanced control\nclass SecureBackupAgent : BackupAgent() {\n override fun onBackup(\n oldState: ParcelFileDescriptor?,\n data: BackupDataOutput?,\n newState: ParcelFileDescriptor?\n ) {\n // Custom backup logic\n // Only backup non-sensitive data\n backupSafePreferences(data)\n }\n \n override fun onRestore(\n data: BackupDataInput?,\n appVersionCode: Int,\n newState: ParcelFileDescriptor?\n ) {\n // Custom restore logic\n restoreSafeData(data)\n }\n}","N/A - XML configuration","Set android:allowBackup='false' or create exclusion rules. Exclude databases, SharedPreferences with tokens/keys, and certificate files.",https://developer.android.com/identity/data/backup
|
|
52
|
+
deep_link_validation,Deep Link Parameter Validation,Android,Security,"deep link validation uri app links intent filter","Validate deep link parameters to prevent injection","// GOOD: Validate all deep link parameters\nclass DeepLinkActivity : AppCompatActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n \n val uri = intent.data\n \n // Step 1: Validate URI exists\n if (uri == null) {\n finish()\n return\n }\n \n // Step 2: Validate scheme and host\n if (uri.scheme != ""https"" || uri.host != ""myapp.com"") {\n Log.w(""DeepLink"", ""Invalid scheme or host: $uri"")\n finish()\n return\n }\n \n // Step 3: Validate path\n val validPaths = setOf(""/product"", ""/user"", ""/article"")\n if (uri.path !in validPaths) {\n finish()\n return\n }\n \n // Step 4: Validate and sanitize parameters\n val itemId = validateItemId(uri.getQueryParameter(""id""))\n val category = validateCategory(uri.getQueryParameter(""category""))\n \n if (itemId == null) {\n finish()\n return\n }\n \n // Safe to use validated data\n loadContent(itemId, category)\n }\n \n private fun validateItemId(id: String?): String? {\n return id?.takeIf { \n it.matches(Regex(""^[a-zA-Z0-9_-]{1,20}$""))\n }\n }\n \n private fun validateCategory(category: String?): String? {\n val allowedCategories = setOf(""books"", ""electronics"", ""clothing"")\n return category?.takeIf { it in allowedCategories }\n }\n}\n\n// AndroidManifest.xml: Verified App Links (Android 6+)\n// <activity android:name="".DeepLinkActivity"">\n// <intent-filter android:autoVerify=""true"">\n// <action android:name=""android.intent.action.VIEW"" />\n// <category android:name=""android.intent.action.DEFAULT"" />\n// <category android:name=""android.intent.action.BROWSABLE"" />\n// \n// <data\n// android:scheme=""https""\n// android:host=""myapp.com""\n// android:pathPrefix=""/product"" />\n// </intent-filter>\n// </activity>\n\n// Digital Asset Links file (.well-known/assetlinks.json on your domain)\n// [{\n// ""relation"": [""delegate_permission/common.handle_all_urls""],\n// ""target"": {\n// ""namespace"": ""android_app"",\n// ""package_name"": ""com.example.myapp"",\n// ""sha256_cert_fingerprints"": [""YOUR_APP_SHA256_FINGERPRINT""]\n// }\n// }]\n\n// Advanced: Handle multiple parameters with validation\nclass SecureDeepLinkHandler {\n data class ValidatedDeepLink(\n val itemId: String,\n val action: String,\n val metadata: Map<String, String>\n )\n \n fun parseAndValidate(uri: Uri): ValidatedDeepLink? {\n // Validate URI structure\n if (!isValidUri(uri)) return null\n \n val itemId = uri.getQueryParameter(""id"")\n ?.takeIf { it.matches(Regex(""^[0-9]+$"")) }\n ?: return null\n \n val action = uri.getQueryParameter(""action"")\n ?.takeIf { it in setOf(""view"", ""edit"", ""share"") }\n ?: ""view"" // Default\n \n // Require authentication for sensitive actions\n if (action == ""edit"" && !isUserAuthenticated()) {\n redirectToLogin()\n return null\n }\n \n return ValidatedDeepLink(itemId, action, emptyMap())\n }\n \n private fun isValidUri(uri: Uri): Boolean {\n return uri.scheme == ""https"" &&\n uri.host == ""myapp.com"" &&\n uri.path?.startsWith(""/"") == true\n }\n}\n\n// BAD: Vulnerable deep link handling\nclass InsecureDeepLinkActivity : AppCompatActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n \n val uri = intent.data\n \n // DANGEROUS: No validation\n val action = uri?.getQueryParameter(""action"")\n val data = uri?.getQueryParameter(""data"")\n \n // Attacker can craft:\n // myapp://malicious?action=delete&data=all\n when (action) {\n ""delete"" -> deleteData(data) // Can delete anything!\n ""execute"" -> executeCommand(data) // Code injection!\n ""redirect"" -> startActivity(Intent(data)) // Intent redirection!\n }\n }\n}","import android.net.Uri\nimport android.content.Intent","Validate scheme, host, and path. Whitelist allowed parameters. Use App Links with Digital Asset Links. Require auth for sensitive actions.",https://developer.android.com/privacy-and-security/risks/unsafe-use-of-deeplinks
|
|
53
|
+
credentials_management,Secure Credential Management,Android,Security,"credentials api key secret android keystore encryptedsharedpreferences hardcoded","Secure storage of credentials and API keys avoiding hardcoding","// GOOD: Store API keys in secrets.properties (gitignored)\n// Step 1: Create secrets.properties (add to .gitignore)\n// API_KEY=your_api_key_here\n// API_SECRET=your_secret_here\n\n// Step 2: Add secrets-gradle-plugin to build.gradle.kts\n// plugins {\n// id(""com.google.android.libraries.mapsplatform.secrets-gradle-plugin"") version ""2.0.1""\n// }\n\n// Step 3: Access in code\nobject ApiConfig {\n val apiKey: String\n get() = BuildConfig.API_KEY\n}\n\n// GOOD: Keystore for runtime secret generation\nclass SecureKeyManager(private val context: Context) {\n private val keyStore = KeyStore.getInstance(""AndroidKeyStore"").apply { load(null) }\n \n fun generateKey(alias: String) {\n if (!keyStore.containsAlias(alias)) {\n val keyGenerator = KeyGenerator.getInstance(\n KeyProperties.KEY_ALGORITHM_AES,\n ""AndroidKeyStore""\n )\n \n val keyGenSpec = KeyGenParameterSpec.Builder(\n alias,\n KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT\n )\n .setBlockModes(KeyProperties.BLOCK_MODE_GCM)\n .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)\n .setUserAuthenticationRequired(false)\n .build()\n \n keyGenerator.init(keyGenSpec)\n keyGenerator.generateKey()\n }\n }\n \n fun encryptData(alias: String, data: String): EncryptedData {\n val cipher = Cipher.getInstance(\n ""AES/GCM/NoPadding""\n )\n cipher.init(Cipher.ENCRYPT_MODE, keyStore.getKey(alias, null))\n \n val encrypted = cipher.doFinal(data.toByteArray())\n return EncryptedData(encrypted, cipher.iv)\n }\n}\n\n// GOOD: EncryptedSharedPreferences for auth tokens\nclass TokenManager(context: Context) {\n private val masterKey = MasterKey.Builder(context)\n .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)\n .build()\n \n private val encryptedPrefs = EncryptedSharedPreferences.create(\n context,\n ""auth_prefs"",\n masterKey,\n EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,\n EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM\n )\n \n fun saveToken(token: String) {\n encryptedPrefs.edit()\n .putString(""auth_token"", token)\n .apply()\n }\n \n fun getToken(): String? {\n return encryptedPrefs.getString(""auth_token"", null)\n }\n \n fun clearToken() {\n encryptedPrefs.edit().remove(""auth_token"").apply()\n }\n}\n\n// BAD: Hardcoded credentials (NEVER DO THIS)\nobject BadApiConfig {\n const val API_KEY = ""sk_live_1234567890abcdefghijk"" // EXPOSED in decompiled APK!\n const val API_SECRET = ""secret_xyz123"" // Anyone can extract this\n}\n\n// BAD: Credentials in BuildConfig committed to git\n// build.gradle.kts:\n// buildConfigField(""String"", ""API_KEY"", ""\\""sk_live_123\\"""") // Committed to version control!\n\n// BAD: Plain SharedPreferences for tokens\nclass InsecureTokenManager(context: Context) {\n private val prefs = context.getSharedPreferences(""prefs"", Context.MODE_PRIVATE)\n \n fun saveToken(token: String) {\n // DANGEROUS: Token readable via ADB backup or rooted device\n prefs.edit().putString(""token"", token).apply()\n }\n}","import androidx.security.crypto.EncryptedSharedPreferences\nimport androidx.security.crypto.MasterKey\nimport android.security.keystore.KeyProperties\nimport android.security.keystore.KeyGenParameterSpec","Use secrets-gradle-plugin for API keys. Use Android Keystore for sensitive keys. EncryptedSharedPreferences for tokens. Never hardcode or commit secrets.",https://developer.android.com/privacy-and-security/security-tips
|
|
54
|
+
runtime_permissions,Runtime Permission Request with Rationale,Android,Security,"runtime permissions rationale shouldshowrequestpermissionrationale contextual","Request runtime permissions contextually with clear user rationale","// GOOD: Context-aware permission request with rationale\nclass LocationFeatureActivity : AppCompatActivity() {\n \n private val locationPermissionLauncher = registerForActivityResult(\n ActivityResultContracts.RequestPermission()\n ) { granted ->\n if (granted) {\n accessLocation()\n } else {\n // Offer alternative or explain limitation\n showAlternativeDialog()\n }\n }\n \n fun requestLocationAccess() {\n when {\n ContextCompat.checkSelfPermission(\n this,\n Manifest.permission.ACCESS_FINE_LOCATION\n ) == PackageManager.PERMISSION_GRANTED -> {\n // Permission already granted\n accessLocation()\n }\n \n shouldShowRequestPermissionRationale(\n Manifest.permission.ACCESS_FINE_LOCATION\n ) -> {\n // Show rationale before requesting\n AlertDialog.Builder(this)\n .setTitle(""Location Access"")\n .setMessage(""We need location access to show nearby stores and delivery estimates."")\n .setPositiveButton(""Allow"") { _, _ ->\n locationPermissionLauncher.launch(\n Manifest.permission.ACCESS_FINE_LOCATION\n )\n }\n .setNegativeButton(""No Thanks"", null)\n .show()\n }\n \n else -> {\n // First time - request directly\n locationPermissionLauncher.launch(\n Manifest.permission.ACCESS_FINE_LOCATION\n )\n }\n }\n }\n \n private fun showAlternativeDialog() {\n AlertDialog.Builder(this)\n .setMessage(""Without location, you can manually enter your address."")\n .setPositiveButton(""Enter Manually"") { _, _ ->\n showManualAddressEntry()\n }\n .setNeutralButton(""Settings"") { _, _ ->\n // Open app settings\n startActivity(Intent(\n Settings.ACTION_APPLICATION_DETAILS_SETTINGS,\n Uri.fromParts(""package"", packageName, null)\n ))\n }\n .show()\n }\n}\n\n// GOOD: Multiple permissions with individual rationales\nclass CameraFeature : Fragment() {\n \n private val cameraPermissions = registerForActivityResult(\n ActivityResultContracts.RequestMultiplePermissions()\n ) { permissions ->\n val cameraGranted = permissions[Manifest.permission.CAMERA] == true\n val storageGranted = permissions[Manifest.permission.WRITE_EXTERNAL_STORAGE] == true\n \n when {\n cameraGranted && storageGranted -> openCamera()\n cameraGranted -> openCameraWithoutSave()\n else -> showPermissionDeniedMessage()\n }\n }\n \n fun capturePhoto() {\n cameraPermissions.launch(arrayOf(\n Manifest.permission.CAMERA,\n Manifest.permission.WRITE_EXTERNAL_STORAGE\n ))\n }\n}\n\n// GOOD: Photo Picker - no permission required (Android 13+)\nclass PhotoSelector : AppCompatActivity() {\n private val pickMedia = registerForActivityResult(\n ActivityResultContracts.PickVisualMedia()\n ) { uri ->\n if (uri != null) {\n processPhoto(uri)\n }\n }\n \n fun selectPhoto() {\n // No permission needed!\n pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))\n }\n}\n\n// BAD: Request all permissions upfront without context\nclass BadPermissionActivity : AppCompatActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n \n // TERRIBLE: Requesting everything at startup\n requestPermissions(\n arrayOf(\n Manifest.permission.CAMERA,\n Manifest.permission.ACCESS_FINE_LOCATION,\n Manifest.permission.READ_CONTACTS,\n Manifest.permission.RECORD_AUDIO,\n Manifest.permission.READ_SMS\n ),\n 0\n )\n }\n \n override fun onRequestPermissionsResult(\n requestCode: Int,\n permissions: Array<String>,\n grantResults: IntArray\n ) {\n // BAD: Don't handle denials gracefully\n if (grantResults.any { it != PackageManager.PERMISSION_GRANTED }) {\n finish() // Just quit the app!\n }\n }\n}","import androidx.activity.result.contract.ActivityResultContracts\nimport android.content.pm.PackageManager\nimport androidx.core.content.ContextCompat","Request permissions contextually when needed. Show clear rationale with shouldShowRequestPermissionRationale. Handle denials gracefully with alternatives.",https://developer.android.com/training/permissions/requesting
|
|
55
|
+
google_play_update,Google Play In-App Updates,Android,Security,"google play in-app update appupdatemanager flexible immediate","Implement secure in-app updates with Google Play","// Add dependency: implementation 'com.google.android.play:app-update:2.1.0'\n// Also: implementation 'com.google.android.play:app-update-ktx:2.1.0'\n\nclass UpdateManager(private val activity: AppCompatActivity) {\n private val appUpdateManager = AppUpdateManagerFactory.create(activity)\n private val updateFlowResultLauncher = activity.registerForActivityResult(\n ActivityResultContracts.StartIntentSenderForResult()\n ) { result ->\n if (result.resultCode != Activity.RESULT_OK) {\n Log.w(""Update"", ""Update flow failed: ${result.resultCode}"")\n }\n }\n \n // Check for updates on app start\n fun checkForUpdate() {\n val appUpdateInfoTask = appUpdateManager.appUpdateInfo\n \n appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->\n when {\n // High-priority update available - use immediate\n appUpdateInfo.updatePriority() >= 4 &&\n appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) -> {\n startImmediateUpdate(appUpdateInfo)\n }\n \n // Regular update - use flexible\n appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE &&\n appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE) -> {\n startFlexibleUpdate(appUpdateInfo)\n }\n \n // Update in progress that was interrupted\n appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS -> {\n startImmediateUpdate(appUpdateInfo)\n }\n }\n }\n }\n \n // Immediate update - blocks app until updated (for critical security patches)\n private fun startImmediateUpdate(appUpdateInfo: AppUpdateInfo) {\n try {\n appUpdateManager.startUpdateFlowForResult(\n appUpdateInfo,\n updateFlowResultLauncher,\n AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build()\n )\n } catch (e: IntentSender.SendIntentException) {\n Log.e(""Update"", ""Update flow error"", e)\n }\n }\n \n // Flexible update - downloads in background, installs when app restarts\n private fun startFlexibleUpdate(appUpdateInfo: AppUpdateInfo) {\n // Register listener to track download progress\n val listener = InstallStateUpdatedListener { state ->\n when (state.installStatus()) {\n InstallStatus.DOWNLOADED -> {\n // Download complete - prompt user to restart\n showUpdateCompleteSnackbar()\n }\n InstallStatus.DOWNLOADING -> {\n // Show progress\n val progress = state.bytesDownloaded() * 100 / state.totalBytesToDownload()\n updateDownloadProgress(progress.toInt())\n }\n InstallStatus.FAILED -> {\n Log.e(""Update"", ""Update failed: ${state.installErrorCode()}"")\n }\n }\n }\n \n appUpdateManager.registerListener(listener)\n \n try {\n appUpdateManager.startUpdateFlowForResult(\n appUpdateInfo,\n updateFlowResultLauncher,\n AppUpdateOptions.newBuilder(AppUpdateType.FLEXIBLE).build()\n )\n } catch (e: IntentSender.SendIntentException) {\n Log.e(""Update"", ""Update flow error"", e)\n }\n }\n \n private fun showUpdateCompleteSnackbar() {\n Snackbar.make(\n activity.findViewById(android.R.id.content),\n ""Update downloaded. Restart to apply."",\n Snackbar.LENGTH_INDEFINITE\n )\n .setAction(""Restart"") {\n appUpdateManager.completeUpdate()\n }\n .show()\n }\n \n // Call in onResume to handle interrupted updates\n fun onResume() {\n appUpdateManager.appUpdateInfo.addOnSuccessListener { info ->\n if (info.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {\n startImmediateUpdate(info)\n } else if (info.installStatus() == InstallStatus.DOWNLOADED) {\n showUpdateCompleteSnackbar()\n }\n }\n }\n}\n\n// Usage in Activity\nclass MainActivity : AppCompatActivity() {\n private lateinit var updateManager: UpdateManager\n \n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n updateManager = UpdateManager(this)\n updateManager.checkForUpdate()\n }\n \n override fun onResume() {\n super.onResume()\n updateManager.onResume()\n }\n}","import com.google.android.play.core.appupdate.*\nimport com.google.android.play.core.install.model.*","Use AppUpdateManager for in-app updates. Immediate mode for critical security patches (priority >= 4). Flexible for regular updates. Handle interrupted flows.",https://developer.android.com/guide/playcore/in-app-updates
|
|
56
|
+
input_sanitization,Comprehensive Input Validation and Sanitization,Android,Security,"input validation sanitization regex whitelist xss injection","Validate and sanitize all user inputs to prevent injection attacks","// GOOD: Input validation utilities\nobject InputValidator {\n \n // Email validation\n fun validateEmail(email: String): String? {\n val emailRegex = Regex(\n ""^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$""\n )\n return email.trim()\n .takeIf { it.length <= 254 && it.matches(emailRegex) }\n }\n \n // Phone number (international format)\n fun validatePhone(phone: String): String? {\n val cleaned = phone.replace(""[^0-9+]"".toRegex(), """")\n return cleaned.takeIf { \n it.matches(Regex(""^\\\\+?[0-9]{10,15}$"")) \n }\n }\n \n // Username (alphanumeric + underscore)\n fun validateUsername(username: String): String? {\n return username.trim()\n .takeIf { \n it.length in 3..20 && \n it.matches(Regex(""^[a-zA-Z0-9_]+$""))\n }\n }\n \n // URL validation\n fun validateUrl(url: String): String? {\n return try {\n val uri = Uri.parse(url)\n url.takeIf { \n uri.scheme in listOf(""https"", ""http"") &&\n !uri.host.isNullOrBlank()\n }\n } catch (e: Exception) {\n null\n }\n }\n \n // Numeric ID\n fun validateId(id: String): Long? {\n return id.toLongOrNull()?.takeIf { it > 0 }\n }\n \n // Text with length limit and allowed characters\n fun validateText(\n text: String,\n maxLength: Int = 500,\n allowSpecialChars: Boolean = false\n ): String? {\n val cleaned = text.trim()\n if (cleaned.length > maxLength) return null\n \n return if (allowSpecialChars) {\n // Remove potentially dangerous characters\n cleaned.replace(""[<>\\""']"".toRegex(), """")\n .takeIf { it.isNotBlank() }\n } else {\n // Only alphanumeric and basic punctuation\n cleaned.takeIf { \n it.matches(Regex(""^[a-zA-Z0-9\\\\s.,!?-]+$""))\n }\n }\n }\n \n // Credit card (basic Luhn validation)\n fun validateCreditCard(cardNumber: String): String? {\n val digits = cardNumber.replace(""\\\\s"".toRegex(), """")\n if (!digits.matches(Regex(""^[0-9]{13,19}$""))) return null\n \n // Luhn algorithm\n var sum = 0\n var alternate = false\n for (i in digits.length - 1 downTo 0) {\n var digit = digits[i].toString().toInt()\n if (alternate) {\n digit *= 2\n if (digit > 9) digit -= 9\n }\n sum += digit\n alternate = !alternate\n }\n \n return digits.takeIf { sum % 10 == 0 }\n }\n}\n\n// GOOD: ViewModel with validation\nclass RegistrationViewModel : ViewModel() {\n \n fun registerUser(email: String, username: String, password: String): Result<User> {\n val validEmail = InputValidator.validateEmail(email)\n ?: return Result.failure(Exception(""Invalid email""))\n \n val validUsername = InputValidator.validateUsername(username)\n ?: return Result.failure(Exception(""Invalid username""))\n \n if (password.length < 8) {\n return Result.failure(Exception(""Password too short""))\n }\n \n // Proceed with registration\n return apiService.register(validEmail, validUsername, password)\n }\n}\n\n// GOOD: Intent validation\nclass SecureActivity : AppCompatActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n \n val userId = intent.getStringExtra(""user_id"")\n ?.let { InputValidator.validateId(it) }\n ?: run {\n finish()\n return\n }\n \n loadUser(userId)\n }\n}\n\n// BAD: No validation\nclass InsecureRegistration : AppCompatActivity() {\n fun register() {\n val email = emailInput.text.toString()\n val username = usernameInput.text.toString()\n \n // DANGEROUS: No validation - can cause:\n // - XSS if displayed in WebView\n // - SQL injection if used in query\n // - Server errors from malformed data\n apiService.register(email, username)\n }\n}","import android.net.Uri\nimport androidx.lifecycle.ViewModel","Always validate inputs with regex and length checks. Whitelist allowed characters. Use takeIf for clean validation. Never trust user input.",https://developer.android.com/privacy-and-security/security-tips#input-validation
|
|
57
|
+
sdk_verification,Third-Party SDK Verification and Integrity,Android,Security,"sdk verification dependency scan owasp vulnerabilities supply chain","Verify third-party SDK integrity and scan for vulnerabilities","// build.gradle.kts - Add dependency check plugin\nplugins {\n id(""org.owasp.dependencycheck"") version ""8.4.0""\n}\n\n// Configure dependency check\ndependencyCheck {\n formats = listOf(""HTML"", ""JSON"", ""XML"")\n scanConfigurations = listOf(""releaseRuntimeClasspath"")\n suppressionFile = ""$projectDir/dependency-check-suppressions.xml""\n failBuildOnCVSS = 7.0f // Fail if high severity found\n}\n\n// Scan dependencies\n// ./gradlew dependencyCheckAnalyze\n\n// GOOD: Verify SDK checksums\nobject SdkVerifier {\n private const val EXPECTED_ANALYTICS_SHA256 = \n ""abc123def456..."" // Get from official SDK documentation\n \n fun verifyAnalyticsSdk(context: Context): Boolean {\n try {\n val packageInfo = context.packageManager.getPackageInfo(\n ""com.analytics.sdk"",\n PackageManager.GET_SIGNATURES\n )\n \n val signature = packageInfo.signatures[0]\n val md = MessageDigest.getInstance(""SHA-256"")\n val digest = md.digest(signature.toByteArray())\n val sha256 = digest.joinToString("""") { ""%02x"".format(it) }\n \n return sha256 == EXPECTED_ANALYTICS_SHA256\n } catch (e: Exception) {\n Log.e(""SDK"", ""Verification failed"", e)\n return false\n }\n }\n}\n\n// GOOD: SDK vetting checklist\nclass SdkVettingProcess {\n data class SdkAudit(\n val name: String,\n val version: String,\n val privacyPolicyReviewed: Boolean,\n val permissionsRequested: List<String>,\n val dataCollected: List<String>,\n val vulnerabilityScanDate: LocalDate,\n val cvssScore: Float,\n val declaredInDataSafety: Boolean\n )\n \n fun auditSdk(sdkName: String): SdkAudit {\n // 1. Review privacy policy\n val privacyPolicyUrl = getSdkPrivacyPolicy(sdkName)\n val privacyReviewed = reviewPrivacyPolicy(privacyPolicyUrl)\n \n // 2. Check permissions requested\n val permissions = extractSdkPermissions(sdkName)\n \n // 3. Identify data collection\n val dataCollected = identifyDataCollection(sdkName)\n \n // 4. Scan for vulnerabilities\n val scanResults = runDependencyCheck()\n \n // 5. Verify Data Safety declaration\n val declaredInPlayConsole = checkDataSafetyDeclaration(sdkName)\n \n return SdkAudit(\n name = sdkName,\n version = getSdkVersion(sdkName),\n privacyPolicyReviewed = privacyReviewed,\n permissionsRequested = permissions,\n dataCollected = dataCollected,\n vulnerabilityScanDate = LocalDate.now(),\n cvssScore = scanResults.maxCvss,\n declaredInDataSafety = declaredInPlayConsole\n )\n }\n}\n\n// GOOD: Gradle version catalog for centralized dependency management\n// gradle/libs.versions.toml\n// [versions]\n// androidx-core = ""1.12.0""\n// security-crypto = ""1.1.0-alpha06""\n// \n// [libraries]\n// androidx-core = { group = ""androidx.core"", name = ""core-ktx"", version.ref = ""androidx-core"" }\n// security-crypto = { group = ""androidx.security"", name = ""security-crypto"", version.ref = ""security-crypto"" }\n\n// build.gradle.kts - Use catalog\ndependencies {\n // Verified trusted libraries\n implementation(libs.androidx.core)\n implementation(libs.security.crypto)\n \n // AVOID: Unknown or unverified SDKs\n // implementation(""com.random:unknown-analytics:1.0.0"") ❌\n}\n\n// GOOD: Runtime SDK behavior monitoring\nclass SdkMonitor : Application() {\n override fun onCreate() {\n super.onCreate()\n \n if (BuildConfig.DEBUG) {\n // Monitor network requests\n monitorNetworkActivity()\n \n // Check for suspicious permissions\n auditRuntimePermissions()\n \n // Log SDK initialization\n logSdkActivity()\n }\n }\n \n private fun monitorNetworkActivity() {\n // Use network interceptor to track SDK endpoints\n val interceptor = object : Interceptor {\n override fun intercept(chain: Interceptor.Chain): Response {\n val request = chain.request()\n Log.d(""SDK_Network"", ""Request: ${request.url}"")\n return chain.proceed(request)\n }\n }\n }\n}\n\n// BAD: No SDK verification\nclass InsecureApp : Application() {\n override fun onCreate() {\n super.onCreate()\n \n // BAD: Initialize unknown SDK without vetting\n UnknownAnalytics.init(""api_key"")\n \n // BAD: Outdated dependency with known CVEs\n // implementation(""com.library:old-version:1.0.0"") // Has CVE-2023-12345\n }\n}","import org.owasp.dependencycheck.gradle.DependencyCheckPlugin\nimport java.security.MessageDigest\nimport java.time.LocalDate","Use OWASP dependency-check plugin. Review SDK privacy policies. Monitor SDK behavior. Verify checksums. Keep dependencies updated. Declare in Data Safety.",https://developer.android.com/privacy-and-security/risks
|
|
58
|
+
swiftui_viewmodel,SwiftUI ViewModel,iOS,Architecture,swiftui viewmodel observable async await published,Standard SwiftUI ViewModel with @Observable (iOS 17+),@Observable\nclass HomeViewModel {\n var items: [Item] = []\n var isLoading = false\n var errorMessage: String?\n\n func loadItems() async {\n isLoading = true\n defer { isLoading = false }\n do {\n items = try await ItemService.shared.fetchItems()\n } catch {\n errorMessage = error.localizedDescription\n }\n }\n}\n\nstruct HomeView: View {\n @State private var viewModel = HomeViewModel()\n\n var body: some View {\n List(viewModel.items) { item in\n Text(item.name)\n }\n .overlay {\n if viewModel.isLoading {\n ProgressView()\n }\n }\n .task { await viewModel.loadItems() }\n }\n},import SwiftUI\nimport Observation,Use @Observable macro (iOS 17+). Use .task for async loading.,https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
|
|
59
|
+
swiftui_navigation,SwiftUI NavigationStack,iOS,Navigation,swiftui navigation stack path destination typesafe,Type-safe NavigationStack with @Binding path,"enum AppRoute: Hashable {\n case detail(itemId: String)\n case settings\n case profile(userId: String)\n}\n\nstruct ContentView: View {\n @State private var path = NavigationPath()\n\n var body: some View {\n NavigationStack(path: $path) {\n List(items) { item in\n NavigationLink(value: AppRoute.detail(itemId: item.id)) {\n Text(item.name)\n }\n }\n .navigationDestination(for: AppRoute.self) { route in\n switch route {\n case .detail(let id): DetailView(itemId: id)\n case .settings: SettingsView()\n case .profile(let id): ProfileView(userId: id)\n }\n }\n .navigationTitle("""Home\"")\n }\n }\n}",import SwiftUI,Use NavigationStack with typed destinations. Avoid NavigationView (deprecated).,https://developer.apple.com/documentation/swiftui/navigationstack
|
|
60
|
+
swiftui_async_image,SwiftUI AsyncImage,iOS,Image,swiftui asyncimage cache loading placeholder phase,AsyncImage with phases for loading/error states,"struct ItemCard: View {\n let item: Item\n\n var body: some View {\n HStack(spacing: 12) {\n AsyncImage(url: URL(string: item.imageUrl)) { phase in\n switch phase {\n case .empty:\n ProgressView()\n .frame(width: 64, height: 64)\n case .success(let image):\n image\n .resizable()\n .aspectRatio(contentMode: .fill)\n .frame(width: 64, height: 64)\n .clipShape(RoundedRectangle(cornerRadius: 8))\n case .failure:\n Image(systemName: \""photo\"")\n .frame(width: 64, height: 64)\n @unknown default:\n EmptyView()\n }\n }\n VStack(alignment: .leading) {\n Text(item.name).font(.headline)\n Text(item.subtitle).font(.subheadline).foregroundStyle(.secondary)\n }\n }\n }\n}",import SwiftUI,Handle all AsyncImage phases. Use Nuke/Kingfisher for caching in production.,https://developer.apple.com/documentation/swiftui/asyncimage
|
|
61
|
+
flutter_bloc_state,Flutter BLoC Pattern,Flutter,Architecture,flutter bloc cubit state event emit freezed,Complete BLoC with events states and Cubit alternative,"// events.dart\nabstract class HomeEvent {}\nclass LoadItems extends HomeEvent {}\nclass RefreshItems extends HomeEvent {}\n\n// state.dart\nabstract class HomeState {}\nclass HomeLoading extends HomeState {}\nclass HomeLoaded extends HomeState {\n final List<Item> items;\n HomeLoaded(this.items);\n}\nclass HomeError extends HomeState {\n final String message;\n HomeError(this.message);\n}\n\n// bloc.dart\nclass HomeBloc extends Bloc<HomeEvent, HomeState> {\n final ItemRepository repository;\n HomeBloc(this.repository) : super(HomeLoading()) {\n on<LoadItems>(_onLoad);\n on<RefreshItems>(_onRefresh);\n }\n\n Future<void> _onLoad(LoadItems event, Emitter<HomeState> emit) async {\n emit(HomeLoading());\n try {\n final items = await repository.getItems();\n emit(HomeLoaded(items));\n } catch (e) {\n emit(HomeError(e.toString()));\n }\n }\n}",import 'package:flutter_bloc/flutter_bloc.dart';,Use Bloc for complex flows with events. Cubit for simpler state management.,https://bloclibrary.dev/
|
|
62
|
+
flutter_go_router,Flutter GoRouter Setup,Flutter,Navigation,flutter gorouter navigation route redirect guard,GoRouter setup with typed routes and auth redirect,"final goRouter = GoRouter(\n initialLocation: '/',\n redirect: (context, state) {\n final isLoggedIn = authBloc.state is Authenticated;\n final isLoginRoute = state.matchedLocation == '/login';\n if (!isLoggedIn && !isLoginRoute) return '/login';\n if (isLoggedIn && isLoginRoute) return '/';\n return null;\n },\n routes: [\n GoRoute(\n path: '/',\n builder: (context, state) => const HomeScreen(),\n routes: [\n GoRoute(\n path: 'detail/:id',\n builder: (context, state) {\n final id = state.pathParameters['id']!;\n return DetailScreen(id: id);\n },\n ),\n ],\n ),\n GoRoute(\n path: '/login',\n builder: (context, state) => const LoginScreen(),\n ),\n ShellRoute(\n builder: (context, state, child) => ScaffoldWithNavBar(child: child),\n routes: [\n GoRoute(path: '/home', builder: (_, __) => const HomeTab()),\n GoRoute(path: '/search', builder: (_, __) => const SearchTab()),\n GoRoute(path: '/profile', builder: (_, __) => const ProfileTab()),\n ],\n ),\n ],\n);",import 'package:go_router/go_router.dart';,Use redirect for auth guards. ShellRoute for bottom navigation. Type-safe params.,https://pub.dev/packages/go_router
|
|
63
|
+
flutter_riverpod,Flutter Riverpod Provider,Flutter,State,flutter riverpod provider notifier asyncvalue future,Riverpod async provider with AsyncValue handling,"// Provider definition\n@riverpod\nclass ItemsNotifier extends _$ItemsNotifier {\n @override\n Future<List<Item>> build() async {\n return ref.read(itemRepositoryProvider).getItems();\n }\n\n Future<void> refresh() async {\n state = const AsyncLoading();\n state = await AsyncValue.guard(\n () => ref.read(itemRepositoryProvider).getItems(),\n );\n }\n}\n\n// UI consumption\nclass HomeScreen extends ConsumerWidget {\n @override\n Widget build(BuildContext context, WidgetRef ref) {\n final itemsAsync = ref.watch(itemsNotifierProvider);\n return itemsAsync.when(\n data: (items) => ListView.builder(\n itemCount: items.length,\n itemBuilder: (_, i) => ItemTile(item: items[i]),\n ),\n loading: () => const Center(child: CircularProgressIndicator()),\n error: (error, stack) => ErrorWidget(error: error, onRetry: () {\n ref.read(itemsNotifierProvider.notifier).refresh();\n }),\n );\n }\n}",import 'package:flutter_riverpod/flutter_riverpod.dart';\nimport 'package:riverpod_annotation/riverpod_annotation.dart';,Use @riverpod code generation. AsyncValue.guard for error handling. when() for exhaustive state matching.,https://riverpod.dev/
|
|
64
|
+
kmp_shared_viewmodel,KMP Shared ViewModel,Cross-platform,Architecture,kmp kotlin multiplatform shared viewmodel expect actual,Kotlin Multiplatform shared ViewModel with expect/actual,"// shared/commonMain\nclass SharedViewModel {\n private val _state = MutableStateFlow<UiState>(UiState.Loading)\n val state: StateFlow<UiState> = _state.asStateFlow()\n\n private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())\n\n fun loadData(repository: DataRepository) {\n scope.launch {\n _state.value = UiState.Loading\n try {\n val data = repository.fetchData()\n _state.value = UiState.Success(data)\n } catch (e: Exception) {\n _state.value = UiState.Error(e.message ?: \""Unknown\"")\n }\n }\n }\n\n fun clear() { scope.cancel() }\n}\n\n// Android usage\nclass AndroidHomeViewModel(\n private val shared: SharedViewModel\n) : ViewModel() {\n val state = shared.state\n override fun onCleared() { shared.clear() }\n}\n\n// iOS usage in SwiftUI\nclass IOSHomeViewModel: ObservableObject {\n @Published var state: UiState = .loading\n private let shared = SharedViewModel()\n private var cancellable: Closeable?\n\n init() {\n cancellable = FlowCollector(\n flow: shared.state,\n callback: { [weak self] state in\n self?.state = state as! UiState\n }\n )\n }\n}",import kotlinx.coroutines.flow.*\nimport kotlinx.coroutines.*,Use StateFlow in shared code. Wrap in AndroidX ViewModel on Android. Use FlowCollector helper on iOS.,https://kotlinlang.org/docs/multiplatform-mobile-getting-started.html
|
|
65
|
+
compose_adaptive_layout,Compose Adaptive Layout,Android,UI,compose adaptive layout windowsize pane detail,Adaptive layout with ListDetailPaneScaffold,"@OptIn(ExperimentalMaterial3AdaptiveApi::class)\n@Composable\nfun AdaptiveItemScreen(viewModel: ItemViewModel = hiltViewModel()) {\n val navigator = rememberListDetailPaneScaffoldNavigator()\n val items by viewModel.items.collectAsStateWithLifecycle()\n var selectedItem by remember { mutableStateOf<Item?>(null) }\n\n ListDetailPaneScaffold(\n directive = navigator.scaffoldDirective,\n value = navigator.scaffoldValue,\n listPane = {\n AnimatedPane {\n LazyColumn {\n items(items, key = { it.id }) { item ->\n ListItem(\n headlineContent = { Text(item.name) },\n modifier = Modifier.clickable {\n selectedItem = item\n navigator.navigateTo(ListDetailPaneScaffoldRole.Detail)\n }\n )\n }\n }\n }\n },\n detailPane = {\n AnimatedPane {\n selectedItem?.let { DetailContent(it) }\n ?: Text(\""Select an item\"")\n }\n }\n )\n}",import androidx.compose.material3.adaptive.*\nimport androidx.compose.material3.adaptive.layout.*\nimport androidx.compose.material3.adaptive.navigation.*,Use ListDetailPaneScaffold for responsive list-detail. Auto adapts to phone/tablet/foldable.,https://developer.android.com/develop/ui/compose/layouts/adaptive
|
|
66
|
+
rn_zustand_store,React Native Zustand Store,React Native,State,zustand store persist middleware react native mmkv,Zustand store with MMKV persistence,"import { create } from 'zustand';\nimport { persist, createJSONStorage } from 'zustand/middleware';\nimport { MMKV } from 'react-native-mmkv';\n\nconst storage = new MMKV();\nconst mmkvStorage = {\n getItem: (name) => storage.getString(name) ?? null,\n setItem: (name, value) => storage.set(name, value),\n removeItem: (name) => storage.delete(name),\n};\n\nconst useAuthStore = create(\n persist(\n (set, get) => ({\n user: null,\n token: null,\n isAuthenticated: false,\n login: async (email, password) => {\n const response = await api.login(email, password);\n set({\n user: response.user,\n token: response.token,\n isAuthenticated: true,\n });\n },\n logout: () => {\n set({ user: null, token: null, isAuthenticated: false });\n },\n }),\n {\n name: 'auth-storage',\n storage: createJSONStorage(() => mmkvStorage),\n partialize: (state) => ({\n user: state.user,\n token: state.token,\n isAuthenticated: state.isAuthenticated,\n }),\n }\n )\n);\n\nexport default useAuthStore;",import { create } from 'zustand';\nimport { persist } from 'zustand/middleware';,Use MMKV for fast persistence. partialize to exclude functions. Use selectors for performance.,https://zustand-demo.pmnd.rs/
|
|
67
|
+
rn_flashlist,React Native FlashList,React Native,List,flashlist react native performance virtualized list,FlashList for high-performance lists,"import { FlashList } from '@shopify/flash-list';\n\nfunction ProductList({ products, onProductPress }) {\n const renderItem = useCallback(({ item }) => (\n <ProductCard product={item} onPress={() => onProductPress(item.id)} />\n ), [onProductPress]);\n\n const keyExtractor = useCallback((item) => item.id, []);\n\n return (\n <FlashList\n data={products}\n renderItem={renderItem}\n keyExtractor={keyExtractor}\n estimatedItemSize={120}\n ItemSeparatorComponent={() => <View style={{ height: 8 }} />}\n ListEmptyComponent={<EmptyState message=\""No products found\"" />}\n onEndReached={loadMore}\n onEndReachedThreshold={0.5}\n />\n );\n}",import { FlashList } from '@shopify/flash-list';\nimport { useCallback } from 'react';,estimatedItemSize is required. Use useCallback for renderItem. 10x faster than FlatList.,https://shopify.github.io/flash-list/
|
|
68
|
+
ios_swiftdata,SwiftData Setup,iOS,Database,swiftdata model container schema migration swift ios 17,SwiftData model with container and query,"@Model\nclass Item {\n var name: String\n var timestamp: Date\n var isCompleted: Bool\n\n init(name: String timestamp: Date = .now isCompleted: Bool = false) {\n self.name = name\n self.timestamp = timestamp\n self.isCompleted = isCompleted\n }\n}\n\n// In App:\n@main\nstruct MyApp: App {\n var body: some Scene {\n WindowGroup {\n ContentView()\n }\n .modelContainer(for: Item.self)\n }\n}\n\n// In View:\nstruct ContentView: View {\n @Query(sort: \\Item.timestamp order: .reverse) private var items: [Item]\n @Environment(\\.modelContext) private var modelContext\n\n var body: some View {\n List(items) { item in\n Text(item.name)\n }\n }\n\n func addItem() {\n let item = Item(name: \""New Item\"")\n modelContext.insert(item)\n }\n}",import SwiftData\nimport SwiftUI,SwiftData replaces CoreData. Use @Model macro. @Query auto-updates UI. iOS 17+ only.,https://developer.apple.com/documentation/swiftdata
|
|
69
|
+
ios_combine_network,iOS Combine Network,iOS,Network,combine publisher urlsession network request decode json ios,Network request with Combine and URLSession,"class APIClient {\n private let session = URLSession.shared\n private let decoder = JSONDecoder()\n\n func fetchItems() -> AnyPublisher<[Item] Error> {\n let url = URL(string: \""https://api.example.com/items\"")!\n return session.dataTaskPublisher(for: url)\n .map(\\.data)\n .decode(type: [Item].self decoder: decoder)\n .receive(on: DispatchQueue.main)\n .eraseToAnyPublisher()\n }\n}\n\n// Usage in ViewModel:\nclass ItemViewModel: ObservableObject {\n @Published var items: [Item] = []\n private var cancellables = Set<AnyCancellable>()\n\n func load() {\n APIClient().fetchItems()\n .sink(\n receiveCompletion: { completion in\n if case .failure(let error) = completion { print(error) }\n },\n receiveValue: { [weak self] items in\n self?.items = items\n }\n )\n .store(in: &cancellables)\n }\n}",import Combine\nimport Foundation,Use async/await on iOS 15+. Combine for reactive streams. Always store in cancellables.,https://developer.apple.com/documentation/combine
|
|
70
|
+
flutter_dio_interceptor,Flutter Dio Interceptor,Flutter,Network,flutter dio interceptor auth token refresh retry network,Dio HTTP client with auth interceptor and token refresh,class AuthInterceptor extends Interceptor {\n final AuthRepository authRepo;\n AuthInterceptor(this.authRepo);\n\n @override\n void onRequest(RequestOptions options RequestInterceptorHandler handler) {\n final token = authRepo.accessToken;\n if (token != null) {\n options.headers['Authorization'] = 'Bearer $token';\n }\n handler.next(options);\n }\n\n @override\n void onError(DioException err ErrorInterceptorHandler handler) async {\n if (err.response?.statusCode == 401) {\n try {\n await authRepo.refreshToken();\n final retryResponse = await dio.fetch(err.requestOptions);\n handler.resolve(retryResponse);\n } catch (e) {\n handler.reject(err);\n }\n } else {\n handler.next(err);\n }\n }\n}\n\n// Setup:\nfinal dio = Dio()..interceptors.add(AuthInterceptor(authRepo));,import 'package:dio/dio.dart';,Add interceptors for auth logging retry. Handle 401 token refresh. Use dio instead of http package.,https://pub.dev/packages/dio
|
|
71
|
+
flutter_freezed_model,Flutter Freezed Model,Flutter,Architecture,flutter freezed sealed union json serializable immutable model,Freezed for immutable models and sealed unions,"@freezed\nclass UserState with _$UserState {\n const factory UserState.initial() = _Initial;\n const factory UserState.loading() = _Loading;\n const factory UserState.loaded(User user) = _Loaded;\n const factory UserState.error(String message) = _Error;\n}\n\n@freezed\nclass User with _$User {\n const factory User({\n required String id,\n required String name,\n required String email,\n @Default(false) bool isPremium,\n }) = _User;\n\n factory User.fromJson(Map<String dynamic> json) => _$UserFromJson(json);\n}\n\n// Usage:\nstate.when(\n initial: () => const SizedBox(),\n loading: () => const CircularProgressIndicator(),\n loaded: (user) => Text(user.name),\n error: (msg) => Text(msg),\n);",import 'package:freezed_annotation/freezed_annotation.dart';\nimport 'package:json_annotation/json_annotation.dart';,Run: dart run build_runner build. Generates copyWith toJson fromJson. Exhaustive when() matching.,https://pub.dev/packages/freezed
|
|
72
|
+
rn_react_query,RN React Query Setup,React Native,Network,react query tanstack fetch cache invalidate mutation stale,React Query for data fetching caching and mutations,"import { useQuery useMutation useQueryClient } from '@tanstack/react-query';\n\nfunction useProducts() {\n return useQuery({\n queryKey: ['products'],\n queryFn: () => fetch('/api/products').then(r => r.json()),\n staleTime: 5 * 60 * 1000, // 5 min\n gcTime: 30 * 60 * 1000,\n });\n}\n\nfunction useAddProduct() {\n const queryClient = useQueryClient();\n return useMutation({\n mutationFn: (product) => fetch('/api/products', {\n method: 'POST',\n body: JSON.stringify(product),\n }),\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['products'] });\n },\n });\n}\n\n// Usage:\nfunction ProductList() {\n const { data, isLoading, error } = useProducts();\n const addProduct = useAddProduct();\n if (isLoading) return <ActivityIndicator />;\n if (error) return <Text>Error: {error.message}</Text>;\n return <FlatList data={data} renderItem={({item}) => <Text>{item.name}</Text>} />;\n}",import { useQuery useMutation useQueryClient } from '@tanstack/react-query';,Wrap app in QueryClientProvider. staleTime prevents refetch. invalidateQueries for cache sync.,https://tanstack.com/query/latest
|
|
73
|
+
rn_navigation_setup,RN Navigation Setup,React Native,Navigation,react navigation stack tab bottom navigator screen typescript,React Navigation with Stack and Bottom Tab setup,const Stack = createNativeStackNavigator();\nconst Tab = createBottomTabNavigator();\n\nfunction HomeTabs() {\n return (\n <Tab.Navigator screenOptions={{ headerShown: false }}>\n <Tab.Screen name='Feed' component={FeedScreen} options={{ tabBarIcon: ({color}) => <Icon name='home' color={color} /> }} />\n <Tab.Screen name='Search' component={SearchScreen} options={{ tabBarIcon: ({color}) => <Icon name='search' color={color} /> }} />\n <Tab.Screen name='Profile' component={ProfileScreen} options={{ tabBarIcon: ({color}) => <Icon name='person' color={color} /> }} />\n </Tab.Navigator>\n );\n}\n\nfunction App() {\n return (\n <NavigationContainer>\n <Stack.Navigator>\n <Stack.Screen name='Main' component={HomeTabs} options={{ headerShown: false }} />\n <Stack.Screen name='Detail' component={DetailScreen} />\n <Stack.Screen name='Settings' component={SettingsScreen} />\n </Stack.Navigator>\n </NavigationContainer>\n );\n},import { NavigationContainer } from '@react-navigation/native';\nimport { createNativeStackNavigator } from '@react-navigation/native-stack';\nimport { createBottomTabNavigator } from '@react-navigation/bottom-tabs';,Use native stack for 60fps transitions. Nest Tab inside Stack. Type routes with TypeScript ParamList.,https://reactnavigation.org/docs/getting-started/
|
|
74
|
+
ios_structured_concurrency,iOS Structured Concurrency,iOS,Concurrency,swift async await task group actor structured concurrency withthrowinggroup,Swift structured concurrency with TaskGroup and actors,"actor ImageCache {\n private var cache: [URL: UIImage] = [:]\n\n func image(for url: URL) -> UIImage? { cache[url] }\n func store(_ image: UIImage for url: URL) { cache[url] = image }\n}\n\nfunc loadDashboard() async throws -> Dashboard {\n async let profile = api.fetchProfile()\n async let feed = api.fetchFeed()\n async let notifications = api.fetchNotifications()\n\n return try await Dashboard(\n profile: profile,\n feed: feed,\n notifications: notifications\n )\n}\n\nfunc loadImages(urls: [URL]) async throws -> [UIImage] {\n try await withThrowingTaskGroup(of: (Int UIImage).self) { group in\n for (i url) in urls.enumerated() {\n group.addTask { (i try await URLSession.shared.image(from: url)) }\n }\n var images = [UIImage?](repeating: nil count: urls.count)\n for try await (index image) in group { images[index] = image }\n return images.compactMap { $0 }\n }\n}",import UIKit,Use async let for parallel. TaskGroup for dynamic parallelism. Actor for thread-safe state.,https://developer.apple.com/documentation/swift/concurrency
|
|
75
|
+
flutter_hive_storage,Flutter Hive Local Storage,Flutter,Storage,flutter hive local storage key value fast nosql box adapter,Hive NoSQL local storage with TypeAdapter,"@HiveType(typeId: 0)\nclass UserSettings extends HiveObject {\n @HiveField(0) late String theme;\n @HiveField(1) late String language;\n @HiveField(2) late bool notificationsEnabled;\n}\n\n// Init in main():\nawait Hive.initFlutter();\nHive.registerAdapter(UserSettingsAdapter());\nawait Hive.openBox<UserSettings>('settings');\n\n// Read/Write:\nfinal box = Hive.box<UserSettings>('settings');\nfinal settings = box.get('current') ?? UserSettings()\n ..theme = 'dark'\n ..language = 'en'\n ..notificationsEnabled = true;\nbox.put('current' settings);\n\n// Reactive:\nValueListenableBuilder(\n valueListenable: box.listenable(),\n builder: (context box _) {\n final s = box.get('current');\n return Text('Theme: ${s?.theme}');\n },\n)",import 'package:hive_flutter/hive_flutter.dart';,10x faster than SharedPreferences. Use for settings cache. Generate adapter with build_runner.,https://pub.dev/packages/hive_flutter
|
|
76
|
+
compose_paging,Compose Paging3 List,Android,List,paging3 compose lazycolumn pagingdata loadstate append refresh,Paging3 integration with LazyColumn showing load states,"@Composable\nfun PagingList(viewModel: ItemViewModel = hiltViewModel()) {\n val items = viewModel.items.collectAsLazyPagingItems()\n\n LazyColumn {\n items(\n count = items.itemCount,\n key = items.itemKey { it.id },\n contentType = items.itemContentType()\n ) { index ->\n items[index]?.let { ItemCard(it) }\n }\n\n when (items.loadState.append) {\n is LoadState.Loading -> item { CircularProgressIndicator(Modifier.fillMaxWidth().padding(16.dp)) }\n is LoadState.Error -> item { RetryButton(onClick = { items.retry() }) }\n else -> {}\n }\n }\n\n if (items.loadState.refresh is LoadState.Loading) {\n Box(Modifier.fillMaxSize() contentAlignment = Alignment.Center) { CircularProgressIndicator() }\n }\n}",import androidx.paging.compose.collectAsLazyPagingItems\nimport androidx.paging.compose.itemKey\nimport androidx.paging.compose.itemContentType,Use itemKey for stable keys. Handle refresh + append states. collectAsLazyPagingItems auto-fetches.,https://developer.android.com/topic/libraries/architecture/paging/v3-paged-data#compose
|
|
77
|
+
compose_bottom_sheet,Compose Modal Bottom Sheet,Android,UI,compose bottom sheet modal material3 scaffold state,Modal bottom sheet with Material3 and proper state handling,"@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun ScreenWithSheet() {\n val sheetState = rememberModalBottomSheetState()\n var showSheet by remember { mutableStateOf(false) }\n\n Scaffold { padding ->\n Button(onClick = { showSheet = true }) {\n Text(\""Show Options\"")\n }\n }\n\n if (showSheet) {\n ModalBottomSheet(\n onDismissRequest = { showSheet = false },\n sheetState = sheetState,\n ) {\n Column(Modifier.padding(16.dp)) {\n Text(\""Options\"" style = MaterialTheme.typography.headlineSmall)\n Spacer(Modifier.height(16.dp))\n ListItem(\n headlineContent = { Text(\""Edit\"") },\n leadingContent = { Icon(Icons.Default.Edit null) },\n modifier = Modifier.clickable { showSheet = false }\n )\n ListItem(\n headlineContent = { Text(\""Delete\"") },\n leadingContent = { Icon(Icons.Default.Delete null) },\n modifier = Modifier.clickable { showSheet = false }\n )\n Spacer(Modifier.height(32.dp))\n }\n }\n }\n}",import androidx.compose.material3.*\nimport androidx.compose.material3.rememberModalBottomSheetState,Use rememberModalBottomSheetState. Control visibility via boolean. Add Spacer at bottom for gesture area.,https://developer.android.com/develop/ui/compose/components/bottom-sheets
|
|
78
|
+
ios_error_handling,iOS Error Handling,iOS,Architecture,swift error handling result custom typed async throw,Typed error handling with custom errors and Result in Swift,"enum AppError: LocalizedError {\n case network(URLError)\n case decoding(Error)\n case unauthorized\n case notFound\n case server(statusCode: Int)\n\n var errorDescription: String? {\n switch self {\n case .network(let error): return \""Network error: \\(error.localizedDescription)\""\n case .decoding: return \""Failed to process server response\""\n case .unauthorized: return \""Please sign in again\""\n case .notFound: return \""Content not found\""\n case .server(let code): return \""Server error (\\(code))\""\n }\n }\n}\n\nclass APIClient {\n func request<T: Decodable>(_ endpoint: Endpoint) async throws(AppError) -> T {\n do {\n let (data response) = try await session.data(for: endpoint.urlRequest)\n guard let httpResponse = response as? HTTPURLResponse else { throw AppError.network(URLError(.badServerResponse)) }\n switch httpResponse.statusCode {\n case 200...299: return try JSONDecoder().decode(T.self from: data)\n case 401: throw AppError.unauthorized\n case 404: throw AppError.notFound\n default: throw AppError.server(statusCode: httpResponse.statusCode)\n }\n } catch let error as AppError { throw error }\n catch let error as URLError { throw .network(error) }\n catch { throw .decoding(error) }\n }\n}",import Foundation,Swift 6 typed throws. Custom error enum. Handle all HTTP status codes.,https://developer.apple.com/documentation/swift/error
|
|
79
|
+
flutter_testing,Flutter Widget Test,Flutter,Testing,flutter widget test pump find matcher bloc provider,Widget testing with BLoC provider and interaction verification,"testWidgets('shows items when loaded' (tester) async {\n final bloc = MockHomeBloc();\n when(() => bloc.state).thenReturn(HomeLoaded([Item(id: '1' name: 'Test')]));\n\n await tester.pumpWidget(\n MaterialApp(\n home: BlocProvider<HomeBloc>.value(\n value: bloc,\n child: const HomeScreen(),\n ),\n ),\n );\n\n expect(find.text('Test') findsOneWidget);\n expect(find.byType(CircularProgressIndicator) findsNothing);\n\n // Interaction test:\n await tester.tap(find.byType(FloatingActionButton));\n verify(() => bloc.add(RefreshItems())).called(1);\n});",import 'package:flutter_test/flutter_test.dart';\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:mocktail/mocktail.dart';,Use mocktail for mocking. Provide BLoC via BlocProvider.value. Always pump before assertions.,https://docs.flutter.dev/testing/overview
|
|
80
|
+
rn_custom_hook,RN Custom Hook,React Native,Architecture,react native custom hook reusable logic composition pattern,Custom hook pattern for reusable logic composition,import { useState useEffect useCallback } from 'react';\n\nfunction useDebounce<T>(value: T delay: number): T {\n const [debouncedValue setDebouncedValue] = useState(value);\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedValue(value) delay);\n return () => clearTimeout(timer);\n } [value delay]);\n return debouncedValue;\n}\n\nfunction usePagination<T>(fetchFn: (page: number) => Promise<T[]>) {\n const [data setData] = useState<T[]>([]);\n const [page setPage] = useState(1);\n const [loading setLoading] = useState(false);\n const [hasMore setHasMore] = useState(true);\n\n const loadMore = useCallback(async () => {\n if (loading || !hasMore) return;\n setLoading(true);\n try {\n const items = await fetchFn(page);\n setData(prev => [...prev ...items]);\n setHasMore(items.length > 0);\n setPage(p => p + 1);\n } finally { setLoading(false); }\n } [page loading hasMore fetchFn]);\n\n return { data loading hasMore loadMore };\n},import { useState useEffect useCallback } from 'react';,Extract reusable logic. useDebounce for search. usePagination for lists. Always cleanup in useEffect.,https://react.dev/learn/reusing-logic-with-custom-hooks
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Name,Category,Keywords,Version Catalog Key,Implementation,KSP/KAPT,Version,Notes,Reference URL
|
|
2
|
+
Jetpack Compose BOM,Compose,"compose bom ui material3 foundation",compose-bom,implementation(platform(libs.compose.bom)),,2024.12.01,BOM manages all Compose versions,https://developer.android.com/develop/ui/compose/bom
|
|
3
|
+
Compose Material3,Compose,"material3 theme color typography",compose-material3,implementation(libs.compose.material3),,BOM,Included via BOM,https://developer.android.com/develop/ui/compose/designsystems/material3
|
|
4
|
+
Compose UI,Compose,"compose ui modifier layout",compose-ui,implementation(libs.compose.ui),,BOM,Included via BOM,https://developer.android.com/develop/ui/compose
|
|
5
|
+
Compose UI Tooling,Compose,"compose preview debug tooling",compose-ui-tooling,debugImplementation(libs.compose.ui.tooling),,BOM,Debug only for preview,https://developer.android.com/develop/ui/compose/tooling
|
|
6
|
+
Compose UI Tooling Preview,Compose,"compose preview annotation",compose-ui-tooling-preview,implementation(libs.compose.ui.tooling.preview),,BOM,For @Preview annotation,https://developer.android.com/develop/ui/compose/tooling/previews
|
|
7
|
+
Compose Icons Extended,Compose,"icons material extended vector",compose-icons-extended,implementation(libs.compose.icons.extended),,BOM,Large library use sparingly,https://developer.android.com/develop/ui/compose/graphics/images/material
|
|
8
|
+
Activity Compose,Compose,"activity compose setcontent",activity-compose,implementation(libs.activity.compose),,1.9.3,Required for setContent in Activity,https://developer.android.com/develop/ui/compose/setup
|
|
9
|
+
Lifecycle Runtime Compose,Lifecycle,"lifecycle compose collectasstate",lifecycle-compose,implementation(libs.lifecycle.runtime.compose),,2.8.7,For collectAsStateWithLifecycle,https://developer.android.com/topic/libraries/architecture/lifecycle
|
|
10
|
+
Lifecycle ViewModel Compose,Lifecycle,"viewmodel compose hiltviewmodel",lifecycle-viewmodel-compose,implementation(libs.lifecycle.viewmodel.compose),,2.8.7,For viewModel() composable,https://developer.android.com/topic/libraries/architecture/viewmodel
|
|
11
|
+
Navigation Compose,Navigation,"navigation compose navhost route",navigation-compose,implementation(libs.navigation.compose),,2.8.5,Type-safe routes with @Serializable,https://developer.android.com/develop/ui/compose/navigation
|
|
12
|
+
Hilt Android,DI,"hilt dagger inject module",hilt-android,implementation(libs.hilt.android),ksp(libs.hilt.compiler),2.51.1,Use with KSP not KAPT,https://developer.android.com/training/dependency-injection/hilt-android
|
|
13
|
+
Hilt Compiler,DI,"hilt compiler ksp kapt processor",hilt-compiler,,ksp(libs.hilt.compiler),2.51.1,Must match hilt-android version,https://developer.android.com/training/dependency-injection/hilt-android
|
|
14
|
+
Hilt Navigation Compose,DI,"hilt navigation viewmodel compose",hilt-navigation-compose,implementation(libs.hilt.navigation.compose),,1.2.0,For hiltViewModel() in Compose,https://developer.android.com/develop/ui/compose/libraries#hilt
|
|
15
|
+
Room Runtime,Database,"room database sqlite orm",room-runtime,implementation(libs.room.runtime),,2.6.1,Use with KSP processor,https://developer.android.com/training/data-storage/room
|
|
16
|
+
Room KTX,Database,"room coroutine flow suspend",room-ktx,implementation(libs.room.ktx),,2.6.1,Coroutine and Flow support,https://developer.android.com/training/data-storage/room
|
|
17
|
+
Room Compiler,Database,"room compiler ksp processor annotation",room-compiler,,ksp(libs.room.compiler),2.6.1,Must match runtime version,https://developer.android.com/training/data-storage/room
|
|
18
|
+
Retrofit,Network,"retrofit http api rest client",retrofit,implementation(libs.retrofit),,2.11.0,Type-safe HTTP client,https://square.github.io/retrofit/
|
|
19
|
+
Retrofit Moshi Converter,Network,"retrofit moshi converter json",retrofit-moshi,implementation(libs.retrofit.converter.moshi),,2.11.0,Moshi JSON converter for Retrofit,https://github.com/square/moshi
|
|
20
|
+
OkHttp,Network,"okhttp client interceptor logging",okhttp,implementation(libs.okhttp),,4.12.0,HTTP client underlying Retrofit,https://square.github.io/okhttp/
|
|
21
|
+
OkHttp Logging,Network,"okhttp logging interceptor debug",okhttp-logging,implementation(libs.okhttp.logging),,4.12.0,Request/response logging for debug,https://square.github.io/okhttp/features/interceptors/
|
|
22
|
+
Moshi Kotlin,Serialization,"moshi json kotlin codegen",moshi-kotlin,implementation(libs.moshi.kotlin),,1.15.1,Use with codegen for performance,https://github.com/square/moshi
|
|
23
|
+
Moshi Codegen,Serialization,"moshi codegen ksp annotation",moshi-codegen,,ksp(libs.moshi.codegen),1.15.1,Generates JSON adapters at compile time,https://github.com/square/moshi#codegen
|
|
24
|
+
Kotlin Serialization,Serialization,"kotlinx serialization json multiplatform",kotlinx-serialization,implementation(libs.kotlinx.serialization.json),,1.7.3,Required for Navigation type-safe routes,https://kotlinlang.org/docs/serialization.html
|
|
25
|
+
Coil Compose,Image,"coil image loading compose async",coil-compose,implementation(libs.coil.compose),,2.7.0,AsyncImage composable for Compose,https://coil-kt.github.io/coil/compose/
|
|
26
|
+
Paging Runtime,Pagination,"paging3 pagination load data",paging-runtime,implementation(libs.paging.runtime),,3.3.5,Core pagination library,https://developer.android.com/topic/libraries/architecture/paging/v3-overview
|
|
27
|
+
Paging Compose,Pagination,"paging compose lazycolumn collectaslazyp",paging-compose,implementation(libs.paging.compose),,3.3.5,LazyPagingItems for Compose,https://developer.android.com/topic/libraries/architecture/paging/v3-overview
|
|
28
|
+
DataStore Preferences,Storage,"datastore preferences key value async",datastore-preferences,implementation(libs.datastore.preferences),,1.1.1,Replaces SharedPreferences,https://developer.android.com/topic/libraries/architecture/datastore
|
|
29
|
+
Coroutines Core,Async,"coroutines core flow channel kotlin",coroutines-core,implementation(libs.coroutines.core),,1.9.0,Core coroutine library,https://kotlinlang.org/docs/coroutines-overview.html
|
|
30
|
+
Coroutines Android,Async,"coroutines android main dispatcher",coroutines-android,implementation(libs.coroutines.android),,1.9.0,Dispatchers.Main for Android,https://developer.android.com/kotlin/coroutines
|
|
31
|
+
WorkManager,Background,"workmanager background task schedule reliable",work-runtime,implementation(libs.work.runtime.ktx),,2.10.0,Reliable background work,https://developer.android.com/develop/background-work/background-tasks/persistent/getting-started
|
|
32
|
+
Timber,Logging,"timber log debug release tree",timber,implementation(libs.timber),,5.0.1,Better logging strip in release,https://github.com/JakeWharton/timber
|
|
33
|
+
Splashscreen,UI,"splashscreen core splash launch startup",splashscreen,implementation(libs.splashscreen),,1.0.1,Material3 splash screen API,https://developer.android.com/develop/ui/views/launch/splash-screen
|
|
34
|
+
LeakCanary,Debug,"leakcanary memory leak detection debug",leakcanary,debugImplementation(libs.leakcanary),,2.14,Debug only auto leak detection,https://square.github.io/leakcanary/
|
|
35
|
+
JUnit,Testing,"junit test unit assert rule",junit,testImplementation(libs.junit),,4.13.2,Standard unit testing,https://junit.org/junit4/
|
|
36
|
+
JUnit5,Testing,"junit5 jupiter test parameterized",junit5,testImplementation(libs.junit.jupiter),,5.10.2,Modern testing with extensions,https://junit.org/junit5/
|
|
37
|
+
MockK,Testing,"mockk mock test kotlin coroutine",mockk,testImplementation(libs.mockk),,1.13.13,Kotlin-first mocking,https://mockk.io/
|
|
38
|
+
Turbine,Testing,"turbine flow test stateflow coroutine",turbine,testImplementation(libs.turbine),,1.2.0,Flow testing made easy,https://github.com/cashapp/turbine
|
|
39
|
+
Coroutines Test,Testing,"coroutines test dispatcher runtest",coroutines-test,testImplementation(libs.coroutines.test),,1.9.0,TestDispatcher and runTest,https://kotlinlang.org/docs/coroutines-test.html
|
|
40
|
+
Compose UI Test,Testing,"compose test ui junit4 semantics rule",compose-ui-test,androidTestImplementation(libs.compose.ui.test.junit4),,BOM,Compose UI testing,https://developer.android.com/develop/ui/compose/testing
|
|
41
|
+
Compose UI Test Manifest,Testing,"compose test manifest activity debug",compose-ui-test-manifest,debugImplementation(libs.compose.ui.test.manifest),,BOM,Required for Compose tests,https://developer.android.com/develop/ui/compose/testing
|
|
42
|
+
Room Testing,Testing,"room test inmemory database migration",room-testing,testImplementation(libs.room.testing),,2.6.1,In-memory DB for tests,https://developer.android.com/training/data-storage/room/testing-db
|
|
43
|
+
Hilt Testing,Testing,"hilt test android inject bindvalue",hilt-android-testing,androidTestImplementation(libs.hilt.android.testing),,2.51.1,Hilt integration testing,https://developer.android.com/training/dependency-injection/hilt-testing
|
|
44
|
+
EncryptedSharedPreferences,Security,"encrypted sharedpreferences security crypto",security-crypto,implementation(libs.security.crypto),,1.1.0-alpha06,Encrypted key-value storage,https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences
|
|
45
|
+
Biometric,Security,"biometric fingerprint face authenticate prompt",biometric,implementation(libs.biometric),,1.2.0-alpha05,Biometric authentication,https://developer.android.com/training/sign-in/biometric-auth
|
|
46
|
+
ExoPlayer Media3,Media,"media3 exoplayer video audio player",media3-exoplayer,implementation(libs.media3.exoplayer),,1.5.0,Modern media playback,https://developer.android.com/media/media3/exoplayer
|
|
47
|
+
Google Maps Compose,Maps,"maps google compose marker location",maps-compose,implementation(libs.maps.compose),,6.2.1,Google Maps for Compose,https://developers.google.com/maps/documentation/android-sdk/maps-compose
|
|
48
|
+
Compose BOM 2025,Compose,compose bom 2025 january material3 foundation,compose-bom,implementation(platform(libs.compose.bom)),,2025.01.01,Latest BOM with Material3 1.3+ and foundation 1.7+,https://developer.android.com/develop/ui/compose/bom
|
|
49
|
+
Navigation Compose 2.8+,Navigation,navigation compose typesafe serializable route 2.8,navigation-compose,implementation(libs.navigation.compose),,2.8.5,Type-safe routes with @Serializable. Requires kotlinx-serialization.,https://developer.android.com/guide/navigation/design/type-safety
|
|
50
|
+
Lifecycle Runtime Compose 2.8+,Lifecycle,lifecycle compose collectasstatewithlifecycle 2.8,lifecycle-compose,implementation(libs.lifecycle.runtime.compose),,2.8.7,collectAsStateWithLifecycle for lifecycle-aware state collection,https://developer.android.com/topic/libraries/architecture/lifecycle
|
|
51
|
+
Room 2.7 Alpha,Database,room 2.7 ksp kotlin multiplatform kmp,room-runtime,implementation(libs.room.runtime),ksp(libs.room.compiler),2.7.0-alpha12,KMP support in alpha. Use KSP instead of KAPT.,https://developer.android.com/training/data-storage/room
|
|
52
|
+
Hilt 2.51,DI,hilt 2.51 dagger ksp inject viewmodel,hilt-android,implementation(libs.hilt.android),ksp(libs.hilt.compiler),2.51.1,KSP support now stable. Migrate from KAPT.,https://developer.android.com/training/dependency-injection/hilt-android
|
|
53
|
+
Kotlin Serialization 1.7,Serialization,kotlinx serialization 1.7 json multiplatform navigation,kotlinx-serialization,implementation(libs.kotlinx.serialization.json),,1.7.3,Required for Navigation 2.8+ type-safe routes,https://kotlinlang.org/docs/serialization.html
|
|
54
|
+
Coil 3,Image,coil3 image compose kmp multiplatform cache,coil3-compose,implementation(libs.coil3.compose),,3.0.4,KMP ready. New API replaces Coil 2.,https://coil-kt.github.io/coil/
|
|
55
|
+
Ktor Client,Network,ktor client multiplatform kmp http engine,ktor-client-core,implementation(libs.ktor.client.core),,3.0.3,KMP HTTP client. Use OkHttp engine for Android.,https://ktor.io/docs/client-create-new-application.html
|
|
56
|
+
Coroutines 1.9,Async,coroutines 1.9 flow channel structured concurrency,coroutines-core,implementation(libs.coroutines.core),,1.9.0,Structured concurrency improvements. Use with Flow.,https://kotlinlang.org/docs/coroutines-overview.html
|
|
57
|
+
Baseline Profile Gradle Plugin,Performance,baseline profile gradle plugin agp startup aot,baselineprofile,baselineProfile(project(':benchmark')),,1.3.3,Automates Baseline Profile generation,https://developer.android.com/topic/performance/baselineprofiles/overview
|
|
58
|
+
Compose Material3 Adaptive,UI,compose material3 adaptive navigationsuite scaffold,material3-adaptive,implementation(libs.material3.adaptive.navigation.suite),,1.3.0,NavigationSuiteScaffold for adaptive layouts,https://developer.android.com/develop/ui/compose/layouts/adaptive
|
|
59
|
+
Health Connect,Health,health connect fitness wearable data google,health-connect,implementation(libs.health.connect),,1.1.0-alpha10,Read/write health and fitness data,https://developer.android.com/health-and-fitness/guides
|
|
60
|
+
Credential Manager,Auth,credential manager passkey password sign-in,credentials,implementation(libs.credentials),,1.3.0,Unified credential API for passkeys and passwords,https://developer.android.com/identity/sign-in/credential-manager
|
|
61
|
+
AndroidX Startup,Startup,startup initializer app contentprovider lazy,startup-runtime,implementation(libs.startup.runtime),,1.2.0,Consolidate component initialization at startup,https://developer.android.com/topic/libraries/app-startup
|
|
62
|
+
Google Play Integrity,Security,play integrity api verification attestation tamper,play-integrity,implementation(libs.play.integrity),,1.4.0,Verify app and device integrity,https://developer.android.com/google/play/integrity
|
|
63
|
+
Compose StrongSkipping,Compose,strong skipping mode recomposition performance stable,compose-compiler,composeCompiler { enableStrongSkippingMode = true },,2.0+,Skip recomposition for unstable composables,https://developer.android.com/develop/ui/compose/performance/stability
|
|
64
|
+
Paparazzi,Testing,paparazzi screenshot test jvm compose fast,paparazzi,testImplementation(libs.paparazzi),,1.3.4,JVM screenshot tests without emulator,https://github.com/cashapp/paparazzi
|
|
65
|
+
Roborazzi,Testing,roborazzi screenshot test robolectric compose,roborazzi,testImplementation(libs.roborazzi),,1.8.0,Robolectric-based screenshot tests,https://github.com/takahirom/roborazzi
|
|
66
|
+
Molecule,State,molecule compose state flow presenter reactive,molecule-runtime,implementation(libs.molecule.runtime),,2.0.0,Build StateFlow from @Composable functions,https://github.com/cashapp/molecule
|
|
67
|
+
Arrow Core,Functional,arrow either raise option functional kotlin,arrow-core,implementation(libs.arrow.core),,1.2.4,Typed error handling with Either and Raise,https://arrow-kt.io/
|
|
68
|
+
Detekt,Code Quality,detekt static analysis kotlin rules custom compose,detekt,detektPlugins(libs.detekt.formatting),,1.23.7,Static analysis for Kotlin with Compose rules,https://detekt.dev/
|
|
69
|
+
Spotless,Code Quality,spotless formatting ktfmt ktlint java kotlin,spotless,"// Plugin: id(""com.diffplug.spotless"")",,7.0.0,Code formatter enforcer for CI,https://github.com/diffplug/spotless
|
|
70
|
+
Kover,Testing,kover coverage kotlin jvm report verify threshold,kover,"// Plugin: id(""org.jetbrains.kotlinx.kover"")",,0.8.3,Kotlin-first code coverage,https://github.com/Kotlin/kotlinx-kover
|
|
71
|
+
Firebase Crashlytics,Monitoring,crashlytics crash reporting firebase analytics,firebase-crashlytics,implementation(libs.firebase.crashlytics),,19.3.0,Real-time crash reporting,https://firebase.google.com/docs/crashlytics
|
|
72
|
+
Firebase Analytics,Analytics,analytics event tracking screen view firebase,firebase-analytics,implementation(libs.firebase.analytics),,22.1.2,App analytics and event tracking,https://firebase.google.com/docs/analytics
|
|
73
|
+
Firebase Remote Config,Configuration,remote config feature flag ab test firebase,firebase-config,implementation(libs.firebase.config),,22.0.1,Feature flags and remote configuration,https://firebase.google.com/docs/remote-config
|
|
74
|
+
Play In-App Updates,Updates,in app update play store flexible immediate,play-app-update,implementation(libs.play.app.update),,2.1.0,Prompt users to update the app,https://developer.android.com/guide/playcore/in-app-updates
|
|
75
|
+
Play In-App Review,Reviews,in app review rating play store prompt,play-review,implementation(libs.play.review),,2.0.2,Request user reviews in-app,https://developer.android.com/guide/playcore/in-app-review
|
|
76
|
+
Lottie Compose,Animation,lottie animation compose json bodymovin after effects,lottie-compose,implementation(libs.lottie.compose),,6.6.0,Lottie animations in Jetpack Compose,https://airbnb.io/lottie/#/android-compose
|
|
77
|
+
Compose Destinations,Navigation,compose destinations ksp navigation type-safe annotation,compose-destinations,implementation(libs.compose.destinations),ksp(libs.compose.destinations.ksp),2.1.0-beta12,KSP-generated type-safe navigation,https://github.com/raamcosta/compose-destinations
|
|
78
|
+
Napier,Logging,napier logging multiplatform kmp kotlin debug,napier,implementation(libs.napier),,2.7.1,KMP logging library,https://github.com/AACupofSunshine/Napier
|
|
79
|
+
Dependency Analysis,Build,dependency analysis unused declared gradle plugin,dependency-analysis,"// Plugin: id(""com.autonomousapps.dependency-analysis"")",,2.4.2,Find unused and mis-declared dependencies,https://github.com/autonomousapps/dependency-analysis-android-gradle-plugin
|