May 10, 2025


 

This information will stroll you thru constructing a small utility step-by-step, specializing in integrating a number of highly effective instruments and ideas important for contemporary Android growth.

What We’ll Cowl:

  • Jetpack Compose: Constructing the UI declaratively.
  • NoSQL Database (Firestore): Storing and retrieving information within the cloud.
  • WorkManager: Working dependable background duties.
  • Construct Flavors: Creating completely different variations of the app (e.g., dev vs. prod).
  • Proguard/R8: Shrinking and obfuscating your code for launch.
  • Firebase App Distribution: Distributing take a look at builds simply.
  • CI/CD (GitHub Actions): Automating the construct and distribution course of.

The Objective: Construct a “Process Reporter” app. Customers can add easy activity descriptions. These duties are saved to Firestore. A background employee will periodically “report” (log a message or replace a counter in Firestore) that the app is energetic. We’ll have dev and prod flavors pointing to completely different Firestore collections/information and distribute the dev construct for testing.

Conditions:

  • Android Studio (newest secure model really helpful).
  • Fundamental understanding of Kotlin and Android growth fundamentals.
  • Familiarity with Jetpack Compose fundamentals (Composable features, State).
  • A Google account to make use of Firebase.
  • A GitHub account (for CI/CD).

Let’s get began!


Step 0: Venture Setup

  1. Create New Venture: Open Android Studio -> New Venture -> Empty Exercise (select Compose).
  2. Identify: AdvancedConceptsApp (or your selection).
  3. Package deal Identify: Your most well-liked package deal identify (e.g., com.yourcompany.advancedconceptsapp).
  4. Language: Kotlin.
  5. Minimal SDK: API 24 or greater.
  6. Construct Configuration Language: Kotlin DSL (construct.gradle.kts).
  7. Click on End.

Step 1: Firebase Integration (Firestore & App Distribution)

  1. Hook up with Firebase: In Android Studio: Instruments -> Firebase.
    • Within the Assistant panel, discover Firestore. Click on “Get Began with Cloud Firestore”. Click on “Hook up with Firebase”. Observe the prompts to create a brand new Firebase challenge or connect with an current one.
    • Click on “Add Cloud Firestore to your app”. Settle for adjustments to your construct.gradle.kts (or construct.gradle) recordsdata. This provides the required dependencies.
    • Return to the Firebase Assistant, discover App Distribution. Click on “Get Began”. Add the App Distribution Gradle plugin by clicking the button. Settle for adjustments.
  2. Allow Providers in Firebase Console:
    • Go to the Firebase Console and choose your challenge.
    • Allow Firestore Database (begin in Check mode).
    • Within the left menu, go to Construct -> Firestore Database. Click on “Create database”.
      • Begin in Check mode for simpler preliminary growth (we’ll safe it later if wanted). Select a location near your customers. Click on “Allow”.
    • Guarantee App Distribution is accessible (no setup wanted right here but).
  3. Obtain Preliminary google-services.json:
    • In Firebase Console -> Venture Settings (gear icon) -> Your apps.
    • Guarantee your Android app (utilizing the bottom package deal identify like com.yourcompany.advancedconceptsapp) is registered. If not, add it.
    • Obtain the google-services.json file.
    • Change Android Studio to the Venture view and place the file contained in the app/ listing.
    • Observe: We’ll probably substitute this file in Step 4 after configuring construct flavors.

Step 2: Constructing the Fundamental UI with Compose

Let’s create a easy UI so as to add and show duties.

  1. Dependencies: Guarantee needed dependencies for Compose, ViewModel, Firestore, and WorkManager are in app/construct.gradle.kts.
    app/construct.gradle.kts
    
    dependencies {
        // Core & Lifecycle & Exercise
        implementation("androidx.core:core-ktx:1.13.1") // Use newest variations
        implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.1")
        implementation("androidx.exercise:activity-compose:1.9.0")
        // Compose
        implementation(platform("androidx.compose:compose-bom:2024.04.01")) // Test newest BOM
        implementation("androidx.compose.ui:ui")
        implementation("androidx.compose.ui:ui-graphics")
        implementation("androidx.compose.ui:ui-tooling-preview")
        implementation("androidx.compose.material3:material3")
        implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.1")
        // Firebase
        implementation(platform("com.google.firebase:firebase-bom:33.0.0")) // Test newest BOM
        implementation("com.google.firebase:firebase-firestore-ktx")
        // WorkManager
        implementation("androidx.work:work-runtime-ktx:2.9.0") // Test newest model
    }
                    

    Sync Gradle recordsdata.

  2. Process Knowledge Class: Create information/Process.kt.
    information/Process.kt
    
    package deal com.yourcompany.advancedconceptsapp.information
    
    import com.google.firebase.firestore.DocumentId
    
    information class Process(
        @DocumentId
        val id: String = "",
        val description: String = "",
        val timestamp: Lengthy = System.currentTimeMillis()
    ) {
        constructor() : this("", "", 0L) // Firestore requires a no-arg constructor
    }
                    
  3. ViewModel: Create ui/TaskViewModel.kt. (We’ll replace the gathering identify later).
    ui/TaskViewModel.kt
    
    package deal com.yourcompany.advancedconceptsapp.ui
    
    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.viewModelScope
    import com.google.firebase.firestore.ktx.firestore
    import com.google.firebase.firestore.ktx.toObjects
    import com.google.firebase.ktx.Firebase
    import com.yourcompany.advancedconceptsapp.information.Process
    // Import BuildConfig later when wanted
    import kotlinx.coroutines.movement.MutableStateFlow
    import kotlinx.coroutines.movement.StateFlow
    import kotlinx.coroutines.launch
    import kotlinx.coroutines.duties.await
    
    // Momentary placeholder - will likely be changed by BuildConfig subject
    const val TEMPORARY_TASKS_COLLECTION = "duties"
    
    class TaskViewModel : ViewModel() {
        non-public val db = Firebase.firestore
        // Use momentary fixed for now
        non-public val tasksCollection = db.assortment(TEMPORARY_TASKS_COLLECTION)
    
        non-public val _tasks = MutableStateFlow>(emptyList())
        val duties: StateFlow> = _tasks
    
        non-public val _error = MutableStateFlow(null)
        val error: StateFlow = _error
    
        init {
            loadTasks()
        }
    
        enjoyable loadTasks() {
            viewModelScope.launch {
                attempt {
                     tasksCollection.orderBy("timestamp", com.google.firebase.firestore.Question.Course.DESCENDING)
                        .addSnapshotListener { snapshots, e ->
                            if (e != null) {
                                _error.worth = "Error listening: ${e.localizedMessage}"
                                return@addSnapshotListener
                            }
                            _tasks.worth = snapshots?.toObjects() ?: emptyList()
                            _error.worth = null
                        }
                } catch (e: Exception) {
                    _error.worth = "Error loading: ${e.localizedMessage}"
                }
            }
        }
    
         enjoyable addTask(description: String) {
            if (description.isBlank()) {
                _error.worth = "Process description can't be empty."
                return
            }
            viewModelScope.launch {
                 attempt {
                     val activity = Process(description = description, timestamp = System.currentTimeMillis())
                     tasksCollection.add(activity).await()
                     _error.worth = null
                 } catch (e: Exception) {
                    _error.worth = "Error including: ${e.localizedMessage}"
                }
            }
        }
    }
                    
  4. Major Display screen Composable: Create ui/TaskScreen.kt.
    ui/TaskScreen.kt
    
    package deal com.yourcompany.advancedconceptsapp.ui
    
    // Imports: androidx.compose.*, androidx.lifecycle.viewmodel.compose.viewModel, java.textual content.SimpleDateFormat, and so forth.
    import androidx.compose.basis.structure.*
    import androidx.compose.basis.lazy.LazyColumn
    import androidx.compose.basis.lazy.objects
    import androidx.compose.material3.*
    import androidx.compose.runtime.*
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.unit.dp
    import androidx.lifecycle.viewmodel.compose.viewModel
    import com.yourcompany.advancedconceptsapp.information.Process
    import java.textual content.SimpleDateFormat
    import java.util.Date
    import java.util.Locale
    import androidx.compose.ui.res.stringResource
    import com.yourcompany.advancedconceptsapp.R // Import R class
    
    @OptIn(ExperimentalMaterial3Api::class) // For TopAppBar
    @Composable
    enjoyable TaskScreen(taskViewModel: TaskViewModel = viewModel()) {
        val duties by taskViewModel.duties.collectAsState()
        val errorMessage by taskViewModel.error.collectAsState()
        var taskDescription by bear in mind { mutableStateOf("") }
    
        Scaffold(
            topBar = {
                TopAppBar(title = { Textual content(stringResource(id = R.string.app_name)) }) // Use useful resource for taste adjustments
            }
        ) { paddingValues ->
            Column(modifier = Modifier.padding(paddingValues).padding(16.dp).fillMaxSize()) {
                // Enter Row
                Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
                    OutlinedTextField(
                        worth = taskDescription,
                        onValueChange = { taskDescription = it },
                        label = { Textual content("New Process Description") },
                        modifier = Modifier.weight(1f),
                        singleLine = true
                    )
                    Spacer(modifier = Modifier.width(8.dp))
                    Button(onClick = {
                        taskViewModel.addTask(taskDescription)
                        taskDescription = ""
                    }) { Textual content("Add") }
                }
                Spacer(modifier = Modifier.peak(16.dp))
                // Error Message
                errorMessage?.let { Textual content(it, colour = MaterialTheme.colorScheme.error, modifier = Modifier.padding(backside = 8.dp)) }
                // Process Listing
                if (duties.isEmpty() && errorMessage == null) {
                    Textual content("No duties but. Add one!")
                } else {
                    LazyColumn(modifier = Modifier.weight(1f)) {
                        objects(duties, key = { it.id }) { activity ->
                            TaskItem(activity)
                            Divider()
                        }
                    }
                }
            }
        }
    }
    
    @Composable
    enjoyable TaskItem(activity: Process) {
        val dateFormat = bear in mind { SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()) }
        Row(modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), verticalAlignment = Alignment.CenterVertically) {
            Column(modifier = Modifier.weight(1f)) {
                Textual content(activity.description, fashion = MaterialTheme.typography.bodyLarge)
                Textual content("Added: ${dateFormat.format(Date(activity.timestamp))}", fashion = MaterialTheme.typography.bodySmall)
            }
        }
    }
                    
  5. Replace MainActivity.kt: Set the content material to TaskScreen.
    MainActivity.kt
    
    package deal com.yourcompany.advancedconceptsapp
    
    import android.os.Bundle
    import androidx.exercise.ComponentActivity
    import androidx.exercise.compose.setContent
    import androidx.compose.basis.structure.fillMaxSize
    import androidx.compose.material3.MaterialTheme
    import androidx.compose.material3.Floor
    import androidx.compose.ui.Modifier
    import com.yourcompany.advancedconceptsapp.ui.TaskScreen
    import com.yourcompany.advancedconceptsapp.ui.theme.AdvancedConceptsAppTheme
    // Imports for WorkManager scheduling will likely be added in Step 3
    
    class MainActivity : ComponentActivity() {
        override enjoyable onCreate(savedInstanceState: Bundle?) {
            tremendous.onCreate(savedInstanceState)
            setContent {
                AdvancedConceptsAppTheme {
                    Floor(modifier = Modifier.fillMaxSize(), colour = MaterialTheme.colorScheme.background) {
                        TaskScreen()
                    }
                }
            }
            // TODO: Schedule WorkManager job in Step 3
        }
    }
                    
  6. Run the App: Check fundamental performance. Duties ought to seem and persist in Firestore’s `duties` assortment (initially).

Step 3: WorkManager Implementation

Create a background employee for periodic reporting.

  1. Create the Employee: Create employee/ReportingWorker.kt. (Assortment identify will likely be up to date later).
    employee/ReportingWorker.kt
    
    package deal com.yourcompany.advancedconceptsapp.employee
    
    import android.content material.Context
    import android.util.Log
    import androidx.work.CoroutineWorker
    import androidx.work.WorkerParameters
    import com.google.firebase.firestore.ktx.firestore
    import com.google.firebase.ktx.Firebase
    // Import BuildConfig later when wanted
    import kotlinx.coroutines.duties.await
    
    // Momentary placeholder - will likely be changed by BuildConfig subject
    const val TEMPORARY_USAGE_LOG_COLLECTION = "usage_logs"
    
    class ReportingWorker(appContext: Context, workerParams: WorkerParameters) :
        CoroutineWorker(appContext, workerParams) {
    
        companion object { const val TAG = "ReportingWorker" }
        non-public val db = Firebase.firestore
    
        override droop enjoyable doWork(): Consequence {
            Log.d(TAG, "Employee began: Reporting utilization.")
            return attempt {
                val logEntry = hashMapOf(
                    "timestamp" to System.currentTimeMillis(),
                    "message" to "App utilization report.",
                    "worker_run_id" to id.toString()
                )
                // Use momentary fixed for now
                db.assortment(TEMPORARY_USAGE_LOG_COLLECTION).add(logEntry).await()
                Log.d(TAG, "Employee completed efficiently.")
                Consequence.success()
            } catch (e: Exception) {
                Log.e(TAG, "Employee failed", e)
                Consequence.failure()
            }
        }
    }
                    
  2. Schedule the Employee: Replace MainActivity.kt‘s onCreate methodology.
    MainActivity.kt additions
    
    // Add these imports to MainActivity.kt
    import android.content material.Context
    import android.util.Log
    import androidx.work.*
    import com.yourcompany.advancedconceptsapp.employee.ReportingWorker
    import java.util.concurrent.TimeUnit
    
    // Inside MainActivity class, after setContent { ... } block in onCreate
    override enjoyable onCreate(savedInstanceState: Bundle?) {
        tremendous.onCreate(savedInstanceState)
        setContent {
            // ... current code ...
        }
        // Schedule the employee
        schedulePeriodicUsageReport(this)
    }
    
    // Add this operate to MainActivity class
    non-public enjoyable schedulePeriodicUsageReport(context: Context) {
        val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .construct()
    
        val reportingWorkRequest = PeriodicWorkRequestBuilder(
                1, TimeUnit.HOURS // ~ each hour
             )
            .setConstraints(constraints)
            .addTag(ReportingWorker.TAG)
            .construct()
    
        WorkManager.getInstance(context).enqueueUniquePeriodicWork(
            ReportingWorker.TAG,
            ExistingPeriodicWorkPolicy.KEEP,
            reportingWorkRequest
        )
        Log.d("MainActivity", "Periodic reporting work scheduled.")
    }
                    
  3. Check WorkManager:
    • Run the app. Test Logcat for messages from ReportingWorker and MainActivity about scheduling.
    • WorkManager duties don’t run instantly, particularly periodic ones. You need to use ADB instructions to pressure execution for testing:
      • Discover your package deal identify: com.yourcompany.advancedconceptsapp
      • Drive run jobs: adb shell cmd jobscheduler run -f com.yourcompany.advancedconceptsapp 999 (The 999 is normally enough, it’s a job ID).
      • Or use Android Studio’s App Inspection tab -> Background Process Inspector to view and set off staff.
    • Test your Firestore Console for the usage_logs assortment.

Step 4: Construct Flavors (dev vs. prod)

Create dev and prod flavors for various environments.

  1. Configure app/construct.gradle.kts:
    app/construct.gradle.kts
    
    android {
        // ... namespace, compileSdk, defaultConfig ...
    
        // ****** Allow BuildConfig technology ******
        buildFeatures {
            buildConfig = true
        }
        // *******************************************
    
        flavorDimensions += "setting"
    
        productFlavors {
            create("dev") {
                dimension = "setting"
                applicationIdSuffix = ".dev" // CRITICAL: Adjustments package deal identify for dev builds
                versionNameSuffix = "-dev"
                resValue("string", "app_name", "Process Reporter (Dev)")
                buildConfigField("String", "TASKS_COLLECTION", ""tasks_dev"")
                buildConfigField("String", "USAGE_LOG_COLLECTION", ""usage_logs_dev"")
            }
            create("prod") {
                dimension = "setting"
                resValue("string", "app_name", "Process Reporter")
                buildConfigField("String", "TASKS_COLLECTION", ""duties"")
                buildConfigField("String", "USAGE_LOG_COLLECTION", ""usage_logs"")
            }
        }
    
        // ... buildTypes, compileOptions, and so forth ...
    }
                    

    Sync Gradle recordsdata.

    Vital: We added applicationIdSuffix = ".dev". This implies the precise package deal identify to your growth builds will turn into one thing like com.yourcompany.advancedconceptsapp.dev. This requires an replace to your Firebase challenge setup, defined subsequent. Additionally word the buildFeatures { buildConfig = true } block which is required to make use of buildConfigField.

  2. Dealing with Firebase for Suffixed Software IDs

    As a result of the `dev` taste now has a distinct utility ID (`…advancedconceptsapp.dev`), the unique `google-services.json` file (downloaded in Step 1) is not going to work for `dev` builds, inflicting a “No matching consumer discovered” error throughout construct.

    You could add this new Software ID to your Firebase challenge:

    1. Go to Firebase Console: Open your challenge settings (gear icon).
    2. Your apps: Scroll right down to the “Your apps” card.
    3. Add app: Click on “Add app” and choose the Android icon (>).
    4. Register dev app:
      • Package deal identify: Enter the precise suffixed ID: com.yourcompany.advancedconceptsapp.dev (substitute `com.yourcompany.advancedconceptsapp` along with your precise base package deal identify).
      • Nickname (Elective): “Process Reporter Dev”.
      • SHA-1 (Elective however Really helpful): Add the debug SHA-1 key from `./gradlew signingReport`.
    5. Register and Obtain: Click on “Register app”. Crucially, obtain the brand new google-services.json file provided. This file now comprises configurations for BOTH your base ID and the `.dev` suffixed ID.
    6. Substitute File: In Android Studio (Venture view), delete the outdated google-services.json from the app/ listing and substitute it with the **newly downloaded** one.
    7. Skip SDK steps: You possibly can skip the remaining steps within the Firebase console for including the SDK.
    8. Clear & Rebuild: Again in Android Studio, carry out a Construct -> Clear Venture after which Construct -> Rebuild Venture.

    Now your challenge is accurately configured in Firebase for each `dev` (with the `.dev` suffix) and `prod` (base package deal identify) variants utilizing a single `google-services.json`.

  3. Create Taste-Particular Supply Units:
    • Change to Venture view in Android Studio.
    • Proper-click on app/src -> New -> Listing. Identify it dev.
    • Inside dev, create res/values/ directories.
    • Proper-click on app/src -> New -> Listing. Identify it prod.
    • Inside prod, create res/values/ directories.
    • (Elective however good observe): Now you can transfer the default app_name string definition from app/src/foremost/res/values/strings.xml into each app/src/dev/res/values/strings.xml and app/src/prod/res/values/strings.xml. Or, you may rely solely on the resValue definitions in Gradle (as achieved above). Utilizing resValue is usually less complicated for single strings like app_name. Should you had many various assets (layouts, drawables), you’d put them within the respective dev/res or prod/res folders.
  4. Use Construct Config Fields in Code:
      • Replace TaskViewModel.kt and ReportingWorker.kt to make use of BuildConfig as a substitute of momentary constants.

    TaskViewModel.kt change

    
    // Add this import
    import com.yourcompany.advancedconceptsapp.BuildConfig
    
    // Substitute the momentary fixed utilization
    // const val TEMPORARY_TASKS_COLLECTION = "duties" // Take away this line
    non-public val tasksCollection = db.assortment(BuildConfig.TASKS_COLLECTION) // Use construct config subject
                        

    ReportingWorker.kt change

    
    // Add this import
    import com.yourcompany.advancedconceptsapp.BuildConfig
    
    // Substitute the momentary fixed utilization
    // const val TEMPORARY_USAGE_LOG_COLLECTION = "usage_logs" // Take away this line
    
    // ... inside doWork() ...
    db.assortment(BuildConfig.USAGE_LOG_COLLECTION).add(logEntry).await() // Use construct config subject
                        

    Modify TaskScreen.kt to doubtlessly use the flavor-specific app identify (although resValue handles this robotically in case you referenced @string/app_name accurately, which TopAppBar normally does). Should you set the title immediately, you’d load it from assets:

     // In TaskScreen.kt (if wanted)
    import androidx.compose.ui.res.stringResource
    import com.yourcompany.advancedconceptsapp.R // Import R class
    // Inside Scaffold -> topBar

    TopAppBar(title = { Textual content(stringResource(id = R.string.app_name)) }) // Use string useful resource

  5. Choose Construct Variant & Check:
    • In Android Studio, go to Construct -> Choose Construct Variant… (or use the “Construct Variants” panel normally docked on the left).
    • Now you can select between devDebug, devRelease, prodDebug, and prodRelease.
    • Choose devDebug. Run the app. The title ought to say “Process Reporter (Dev)”. Knowledge ought to go to tasks_dev and usage_logs_dev in Firestore.
    • Choose prodDebug. Run the app. The title needs to be “Process Reporter”. Knowledge ought to go to duties and usage_logs.

Step 5: Proguard/R8 Configuration (for Launch Builds)

R8 is the default code shrinker and obfuscator in Android Studio (successor to Proguard). It’s enabled by default for launch construct sorts. We have to guarantee it doesn’t break our app, particularly Firestore information mapping.

    1. Overview app/construct.gradle.kts Launch Construct Kind:
      app/construct.gradle.kts
      
      android {
          // ...
          buildTypes {
              launch {
                  isMinifyEnabled = true // Ought to be true by default for launch
                  isShrinkResources = true // R8 handles each
                  proguardFiles(
                      getDefaultProguardFile("proguard-android-optimize.txt"),
                      "proguard-rules.professional" // Our customized guidelines file
                  )
              }
              debug {
                  isMinifyEnabled = false // Often false for debug
                  proguardFiles(
                      getDefaultProguardFile("proguard-android-optimize.txt"),
                      "proguard-rules.professional"
                  )
              }
              // ... debug construct sort ...
          }
          // ...
      }
                 

      isMinifyEnabled = true allows R8 for the launch construct sort.

    2. Configure app/proguard-rules.professional:
      • Firestore makes use of reflection to serialize/deserialize information lessons. R8 may take away or rename lessons/fields wanted for this course of. We have to add “preserve” guidelines.
      • Open (or create) the app/proguard-rules.professional file. Add the next:
      
      # Hold Process information class and its members for Firestore serialization
      -keep class com.yourcompany.advancedconceptsapp.information.Process { (...); *; }
      # Hold another information lessons used with Firestore equally
      # -keep class com.yourcompany.advancedconceptsapp.information.AnotherFirestoreModel { (...); *; }
      
      # Hold Coroutine builders and intrinsics (typically wanted, although AGP/R8 deal with some robotically)
      -keepnames class kotlinx.coroutines.intrinsics.** { *; }
      
      # Hold companion objects for Staff if wanted (generally R8 removes them)
      -keepclassmembers class * extends androidx.work.Employee {
          public static ** Companion;
      }
      
      # Hold particular fields/strategies if utilizing reflection elsewhere
      # -keepclassmembers class com.instance.SomeClass {
      #    non-public java.lang.String someField;
      #    public void someMethod();
      # }
      
      # Add guidelines for another libraries that require them (e.g., Retrofit, Gson, and so forth.)
      # Seek the advice of library documentation for needed Proguard/R8 guidelines.
    • Rationalization:
      • -keep class ... { (...); *; }: Retains the Process class, its constructors (), and all its fields/strategies (*) from being eliminated or renamed. That is essential for Firestore.
      • -keepnames: Prevents renaming however permits elimination if unused.
      • -keepclassmembers: Retains particular members inside a category.

3. Check the Launch Construct:

    • Choose the prodRelease construct variant.
    • Go to Construct -> Generate Signed Bundle / APK…. Select APK.
    • Create a brand new keystore or use an current one (comply with the prompts). Bear in mind the passwords!
    • Choose prodRelease because the variant. Click on End.
    • Android Studio will construct the discharge APK. Discover it (normally in app/prod/launch/).
    • Set up this APK manually on a tool: adb set up app-prod-release.apk.
    • Check completely. Are you able to add duties? Do they seem? Does the background employee nonetheless log to Firestore (examine usage_logs)? If it crashes or information doesn’t save/load accurately, R8 probably eliminated one thing vital. Test Logcat for errors (typically ClassNotFoundException or NoSuchMethodError) and modify your proguard-rules.professional file accordingly.

 


 

Step 6: Firebase App Distribution (for Dev Builds)

Configure Gradle to add growth builds to testers by way of Firebase App Distribution.

  1. Obtain non-public key: on Firebase console go to Venture Overview  at left high nook -> Service accounts -> Firebase Admin SDK -> Click on on “Generate new non-public key” button ->
    api-project-xxx-yyy.json transfer this file to root challenge on the identical stage of app folder *Be sure that this file be in your native app, don't push it to the distant repository as a result of it comprises smart information and will likely be rejected later
  2. Configure App Distribution Plugin in app/construct.gradle.kts:
    app/construct.gradle.kts
    
    // Apply the plugin on the high
    plugins {
        // ... different plugins id("com.android.utility"), id("kotlin-android"), and so forth.
        alias(libs.plugins.google.firebase.appdistribution)
    }
    
    android {
        // ... buildFeatures, flavorDimensions, productFlavors ...
    
        buildTypes {
            getByName("launch") {
                isMinifyEnabled = true // Ought to be true by default for launch
                isShrinkResources = true // R8 handles each
                proguardFiles(
                    getDefaultProguardFile("proguard-android-optimize.txt"),
                    "proguard-rules.professional" // Our customized guidelines file
                )
            }
            getByName("debug") {
                isMinifyEnabled = false // Often false for debug
                proguardFiles(
                    getDefaultProguardFile("proguard-android-optimize.txt"),
                    "proguard-rules.professional"
                )
            }
            firebaseAppDistribution {
                artifactType = "APK"
                releaseNotes = "Newest construct with fixes/options"
                testers = "briew@instance.com, bri@instance.com, cal@instance.com"
                serviceCredentialsFile="$rootDir/api-project-xxx-yyy.json"//don't push this line to the distant repository or stablish as native variable } } } 

    Add library model to libs.model.toml

    
    [versions]
    googleFirebaseAppdistribution = "5.1.1"
    [plugins]
    google-firebase-appdistribution = { id = "com.google.firebase.appdistribution", model.ref = "googleFirebaseAppdistribution" }
    
    Make sure the plugin classpath is within the 

    project-level

     construct.gradle.kts: 

    challenge construct.gradle.kts

    
    plugins {
        // ...
        alias(libs.plugins.google.firebase.appdistribution) apply false
    }
                    

    Sync Gradle recordsdata.

  3. Add a Construct Manually:
    • Choose the specified variant (e.g., devDebugdevRelease, prodDebug , prodRelease).
    • In Android Studio Terminal  run  every commmand to generate apk model for every setting:
      • ./gradlew assembleRelease appDistributionUploadProdRelease
      • ./gradlew assembleRelease appDistributionUploadDevRelease
      • ./gradlew assembleDebug appDistributionUploadProdDebug
      • ./gradlew assembleDebug appDistributionUploadDevDebug
    • Test Firebase Console -> App Distribution -> Choose .dev challenge . Add testers or use the configured group (`android-testers`).

Step 7: CI/CD with GitHub Actions

Automate constructing and distributing the `dev` construct on push to a particular department.

  1. Create GitHub Repository. Create a brand new repository on GitHub and push your challenge code to it.
    1. Generate FIREBASE_APP_ID:
      • on Firebase App Distribution go to Venture Overview -> Normal -> App ID for com.yourcompany.advancedconceptsapp.dev setting (1:xxxxxxxxx:android:yyyyyyyyyy)
      • In GitHub repository go to Settings -> Secrets and techniques and variables -> Actions -> New repository secret
      • Set the identify: FIREBASE_APP_ID and worth: paste the App ID generated
    2. Add FIREBASE_SERVICE_ACCOUNT_KEY_JSON:
      • open api-project-xxx-yyy.json situated at root challenge and replica the content material
      • In GitHub repository go to Settings -> Secrets and techniques and variables -> Actions -> New repository secret
      • Set the identify: FIREBASE_SERVICE_ACCOUNT_KEY_JSON and worth: paste the json content material
    3. Create GitHub Actions Workflow File:
      • In your challenge root, create the directories .github/workflows/.
      • Inside .github/workflows/, create a brand new file named android_build_distribute.yml.
      • Paste the next content material:
    4. 
      identify: Android CI 
      
      on: 
        push: 
          branches: [ "main" ] 
        pull_request: 
          branches: [ "main" ] 
      jobs: 
        construct: 
          runs-on: ubuntu-latest 
          steps: 
          - makes use of: actions/checkout@v3
          - identify: arrange JDK 17 
            makes use of: actions/setup-java@v3 
            with: 
              java-version: '17' 
              distribution: 'temurin' 
              cache: gradle 
          - identify: Grant execute permission for gradlew 
            run: chmod +x ./gradlew 
          - identify: Construct devRelease APK 
            run: ./gradlew assembleRelease 
          - identify: add artifact to Firebase App Distribution
            makes use of: wzieba/Firebase-Distribution-Github-Motion@v1
            with:
              appId: ${{ secrets and techniques.FIREBASE_APP_ID }}
              serviceCredentialsFileContent: ${{ secrets and techniques.FIREBASE_SERVICE_ACCOUNT_KEY_JSON }}
              teams: testers
              file: app/construct/outputs/apk/dev/launch/app-dev-release-unsigned.apk
      
    1. Commit and Push: Commit the .github/workflows/android_build_distribute.yml file and push it to your foremost department on GitHub.
    1. Confirm: Go to the “Actions” tab in your GitHub repository. It is best to see the workflow working. If it succeeds, examine Firebase App Distribution for the brand new construct. Your testers ought to get notified.

 


 

Step 8: Testing and Verification Abstract

    • Flavors: Change between devDebug and prodDebug in Android Studio. Confirm the app identify adjustments and information goes to the right Firestore collections (tasks_dev/duties, usage_logs_dev/usage_logs).
    • WorkManager: Use the App Inspection -> Background Process Inspector or ADB instructions to confirm the ReportingWorker runs periodically and logs information to the appropriate Firestore assortment based mostly on the chosen taste.
    • R8/Proguard: Set up and take a look at the prodRelease APK manually. Guarantee all options work, particularly including/viewing duties (Firestore interplay). Test Logcat for crashes associated to lacking lessons/strategies.
    • App Distribution: Be certain that testers obtain invitations for the devDebug (or devRelease) builds uploaded manually or by way of CI/CD. Guarantee they will set up and run the app.
    • CI/CD: Test the GitHub Actions logs for profitable builds and uploads after pushing to the develop department. Confirm the construct seems in Firebase App Distribution.

 

Conclusion

Congratulations! You’ve navigated complicated Android subjects together with Firestore, WorkManager, Compose, Flavors (with appropriate Firebase setup), R8, App Distribution, and CI/CD.

This challenge supplies a strong basis. From right here, you may discover:

    • Extra complicated WorkManager chains or constraints.
    • Deeper R8/Proguard rule optimization.
    • Extra subtle CI/CD pipelines (deploy signed apks/bundles, working checks, deploying to Google Play).
    • Utilizing completely different NoSQL databases or native caching with Room.
    • Superior Compose UI patterns and state administration.
    • Firebase Authentication, Cloud Capabilities, and so forth.

If you wish to have entry to the total code in my GitHub repository, contact me within the feedback.


 

Venture Folder Construction (Conceptual)


AdvancedConceptsApp/
├── .git/
├── .github/workflows/android_build_distribute.yml
├── .gradle/
├── app/
│   ├── construct/
│   ├── libs/
│   ├── src/
│   │   ├── foremost/           # Widespread code, res, AndroidManifest.xml
│   │   │   └── java/com/yourcompany/advancedconceptsapp/
│   │   │       ├── information/Process.kt
│   │   │       ├── ui/TaskScreen.kt, TaskViewModel.kt, theme/
│   │   │       ├── employee/ReportingWorker.kt
│   │   │       └── MainActivity.kt
│   │   ├── dev/            # Dev taste supply set (optionally available overrides)
│   │   ├── prod/           # Prod taste supply set (optionally available overrides)
│   │   ├── take a look at/           # Unit checks
│   │   └── androidTest/    # Instrumentation checks
│   ├── google-services.json # *** IMPORTANT: Comprises configs for BOTH package deal names ***
│   ├── construct.gradle.kts    # App-level construct script
│   └── proguard-rules.professional # R8/Proguard guidelines
├── api-project-xxx-yyy.json # Firebase service account key json
├── gradle/wrapper/
├── construct.gradle.kts      # Venture-level construct script
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
        

 





Supply hyperlink

Leave a Comment