Common Android Development Setup and Practices

Table of Contents

  1. Initial Environment Setup

    1.1. System Requirements

    1.2. Setting Up the Development Environment

    1.3. Configuring the Development Environment

    1.4. Cloning the Repository and Project Setup

    1.5. Troubleshooting Common Setup Issues

  2. Running Your First Build

    2.1. Building the Project

    2.2. Running the Project

    2.3. Configuring the Release Key

    2.4. Switching Build Variants

  3. Building the Application for Release or Testing

    3.1. Updating Version Information

    3.2. Selecting the Build Variant

    3.3. Building the App Bundle

    3.4. Generating a Signed App Bundle

    3.5. Understanding Build Configurations and ProGuard Settings

  4. Distributing Builds via Google Play Console

    4.1. Navigating to the PokerSnowie App in Google Play Console

    4.2. Managing Production Releases

    4.3. Managing Internal Testing

  5. UI Components Documentation

    5.1. Overview

    5.2. Understanding Jetpack Compose

  6. Resources Directory

    6.1. Overview

    6.2. Drawable Resources

    6.3. Font Resources

    6.4. Raw Resources

Initial Environment Setup

System Requirements

Hardware Requirements

  • Windows:

    • OS: 64-bit Microsoft® Windows® 8/10/11

    • Processor: x86_64 CPU architecture; 2nd generation Intel Core or newer, or AMD CPU with support for a Windows Hypervisor

    • RAM: Minimum 8 GB (16 GB recommended)

    • Disk Space: 8 GB of available disk space minimum (IDE + Android SDK + Android Emulator)

    • Resolution: 1280 x 800 minimum screen resolution

  • macOS:

    • OS: MacOS® 10.14 (Mojave) or higher

    • Processor: ARM-based chips, or 2nd generation Intel Core or newer with support for Hypervisor Framework

    • RAM: Minimum 8 GB (16 GB recommended)

    • Disk Space: 8 GB of available disk space minimum (IDE + Android SDK + Android Emulator)

    • Resolution: 1280 x 800 minimum screen resolution

  • Linux:

    • OS: Any 64-bit Linux distribution that supports Gnome, KDE, or Unity DE; GNU C Library (glibc) 2.31 or later.

    • Processor: x86_64 CPU architecture; 2nd generation Intel Core or newer, or AMD processor with support for AMD Virtualization (AMD-V) and SSSE3

    • RAM: Minimum 8 GB (16 GB recommended)

    • Disk Space: 8 GB of available disk space minimum (IDE + Android SDK + Android Emulator)

    • Resolution: 1280 x 800 minimum screen resolution

Software Requirements

  • Java Development Kit (JDK): JDK 11 or newer

  • Android Studio: The latest stable version

Setting Up the Development Environment

Installing Android Studio

  1. Download Android Studio:

    • Visit the official Android Studio website at Android Studio to download the installer for your operating system.

  2. Installation:

    • Follow the platform-specific instructions to install Android Studio and any recommended settings.

    • Windows: Run the .exe file downloaded.

    • macOS: Open the .dmg file and drag Android Studio to the Applications folder.

    • Linux: Extract the .tar.gz file and run the studio.sh script from the android-studio/bin directory.

Initial Setup

  • Upon the first launch, follow the Android Studio setup wizard to install the Android SDK, Android SDK Platform-Tools, and Android SDK Build-Tools.

  • Configure an emulator by selecting an appropriate device profile and Android version in the AVD Manager.

Configuring the Development Environment

SDK Manager

  • Navigate to Tools > SDK Manager to ensure the correct SDK platforms are installed for the Android versions you need to support.

  • Install or update any additional SDK tools that are required.

Emulator Setup

  • Go to Tools > AVD Manager and create a new Android Virtual Device (AVD).

  • Choose a device definition (e.g., Pixel 3) and select the system image (e.g., Android 11).

Gradle Configuration

  • In File > Project Structure > Project, set the Gradle version and Android Plugin version recommended for your version of Android Studio.

  • Adjust build.gradle to include dependencies and configure build variants specific to the PokerSnowie application.

Cloning the Repository and Project Setup

  1. Clone the Repository:

  2. Project Configuration:

    • Allow Android Studio to import the project and auto-configure based on the existing build.gradle files.

    • Ensure all dependencies are correctly configured and up to date by referring to build.gradle.

Troubleshooting Common Setup Issues

  • Check the SDK Manager for updates or missing components if you encounter issues with the Android SDK.

  • For Gradle build errors, verify the Gradle configuration in build.gradle and ensure you are using compatible versions of the Android Gradle plugin.

Running Your First Build

Building the Project

  • Manual Build:

    • Navigate to Build > Make Project in the menu to compile the project and download the necessary dependencies.

  • Using Gradle:

    • Open the terminal within Android Studio and run ./gradlew assembleDebug to build the debug APK.

Running the Project

  • Using the Run Button:

    • Select the target device from the dropdown menu in the toolbar.

    • Click the "Play" button (▶) in the toolbar to run the app on the selected emulator or connected device.

  • Using the Menu:

    • Navigate to Run > Run 'app' from the menu after selecting your target device to launch the application.

  • From the Terminal:

    • You can also run the application from the terminal within Android Studio by typing ./gradlew installDebug after building the APK to install it on a connected device or active emulator.

Configuring Release Key

  • Release Key Setup:

    • A keystore file PokerSnowieRelease.keystore is provided in the android-pokersnowie/app directory. This file contains the keys used to sign your application in release mode.

    • The signing configuration is specified in the build.gradle file within the android block as follows:

      signingConfigs {
          release {
              storeFile file('******.keystore')
              storePassword '*******'
              keyAlias '*******'
              keyPassword '*******'
          }
      }

Switching Build Variants

  • To switch between debug and release build variants:

    • Open Android Studio and go to the "Build Variants" window, usually located on the left side of the workspace.

    • In the "Build Variants" panel, you will see a list of modules and their corresponding build variants. Select the app module.

    • Click on the dropdown menu under the "Active Build Variant" column for the app module.

    • Choose either debug or release to switch between development and production configurations. The IDE will automatically apply the change and re-sync the project.

This method allows you to test how your app behaves in both development and production-like environments, utilizing the appropriate build variant for your current development phase.

Building the Application for Release or Testing

Updating Version Information

Before creating a new release or debug build, you must update the version information to ensure each build is uniquely identifiable.

  1. Update build.gradle:

    • Navigate to app/build.gradle.

    • In the defaultConfig block, update the versionCode and versionName:

      defaultConfig {
          applicationId "com.pokersnowie.client.android"
          minSdk 24
          targetSdk 34
          versionCode 2000013  // Increment this for each new version
          versionName "2.1"    // Update this to reflect the new version
          testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
          vectorDrawables.useSupportLibrary true
          signingConfig signingConfigs.release
      }
  2. Update User-Facing Version Information:

    • Navigate to app/src/main/java/com.pokersnowie.client/android/pages/Options.

    • At the bottom of the file, update the build number displayed to the user:

      Text(
          text = "2.1 build 13",
          style = ExtraTypographyStyles["smallL"]!!,
          color = snwWarmGrey,
          modifier = Modifier.align(Alignment.Center)
      )

Selecting the Build Variant

To prepare your application for a release build or internal testing, you first need to select the appropriate build variant.

  1. Open the "Build Variants" window:

    • Navigate to the "Build Variants" window in Android Studio, typically located at the bottom-left of the workspace.

  2. Choose the Build Variant:

    • Find the app module in the "Build Variants" panel.

    • Use the dropdown menu under "Active Build Variant" to select either debug for testing or release for the final release build.

    • Android Studio will automatically apply the change and synchronize the project.

Building the App Bundle

After selecting the desired build variant, you need to build the App Bundle, which is the preferred format for distributing the application via the Google Play Store.

  1. Build the App Bundle:

    • Navigate to Build > Build Bundle(s) / APK(s) > Build Bundle(s).

    • Choose the module for which you want to build the bundle if prompted.

Generating a Signed App Bundle

For the release build, you must generate a signed App Bundle. This ensures that the app is securely signed with your PokerSnowie release key.

  1. Prepare the Keystore:

    • Ensure the keystore file, PokerSnowieRelease.keystore, is located in the android-pokersnowie/app directory and properly referenced in your build.gradle file as previously set up.

  2. Generate Signed Bundle:

    • Navigate to Build > Generate Signed Bundle / APK.

    • Select Android App Bundle as the build type and click Next.

    • In the dialog that appears, specify the path to your keystore, the key alias, and enter the passwords for both the keystore and the key.

    • Choose the build variant (release) and click Next.

    • Follow the prompts to complete the signing process.

  3. Verify the Bundle:

    • After the signing process is complete, verify the generated App Bundle by locating it in the specified output folder, typically located in android-pokersnowie/app/build/outputs/bundle/{release/debug}.

Understanding Build Configurations and ProGuard Settings

Build Types Configuration

The build.gradle file within the PokerSnowie project specifies different settings for debug and release build types to optimize and protect the application according to the needs of the development and production environments.

  • Debug Build Configuration:

    debug { 
        debuggable true 
        buildConfigField "Boolean", "DEBUG_MODE", "true" 
    }
  • Release Build Configuration:

    release {
        minifyEnabled true
        shrinkResources true
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
    • minifyEnabled true: Activates code shrinking, which removes unused code and resources, helping to reduce APK size and potentially deter reverse engineering.

    • shrinkResources true: Further reduces APK size by aggressively removing unused resources.

    • proguardFiles: Specifies ProGuard configuration files to control the obfuscation and optimization process, crucial for protecting the app from reverse engineering.

Proguard Configuration

ProGuard is a tool used to minify and obfuscate Java bytecode, which helps to protect your application against reverse engineering. The proguard-rules.pro file contains rules that define which parts of the code should be preserved from obfuscation. Here are key rules and their purposes:

  • General ProGuard Rules:

    -keep class SGO.** { *; }
    -keep class Snowie.** { *; }
    -keepclassmembers class SGO.** { ... }
    -keepclassmembers class Snowie.** { ... }

    These rules ensure that classes and members related to the PokerSnowie engine, specifically the SGO and Snowie namespaces are not removed or renamed during the obfuscation process, which is crucial for maintaining functionality that relies on reflection or is accessed externally.

  • Specific Rule Examples:

    -keep class kotlin.Metadata { *; }
    -keepattributes Signature, *Annotation*, EnclosingMethod
    
    • keep class: Prevents classes from being removed or renamed.

    • keepattributes: Ensures that certain attributes in the bytecode are preserved, which are necessary for Kotlin reflection and some Java functionalities.

  • Custom Rules for App Components:

    -keep class com.pokersnowie.client.android.pages.ScoreboardKt { *; }
    -keep class com.pokersnowie.client.android.components.ScoreboardCardKt { *; }
    

    These rules protect specific app components and custom views from being obfuscated, ensuring that they function as expected without issues related to name mangling.

Make sure to review and update these configurations and rules as you add new features or dependencies to your application to maintain its integrity and performance.

Distributing Builds via Google Play Console

Navigating to the PokerSnowie App in Google Play Console

  1. Access the Google Play Console:

    • Log in to your developer account at Google Play Console.

    • Select the PokerSnowie application from the list of apps under your developer account.

Managing Production Releases

For release builds intended for full public distribution, follow these steps to update the application in the Production track:

  1. Go to Production:

    • Inside the PokerSnowie app dashboard, navigate to the Release section in the left sidebar.

    • Click on Production to manage the production releases.

  2. Create a New Release:

    • Click on Create new release.

    • If prompted, review and accept any required agreements.

  3. Upload the App Bundle:

    • Click on Upload to select the generated .aab file (Android App Bundle) from your local system.

    • Once the upload is complete, the console will process the bundle and display the version details.

  4. Set Release Details:

    • Provide release notes for this version which describe the changes or updates made.

  5. Review and Send for Review:

    • Review all the details, then click Review release at the bottom of the page.

    • Once everything is confirmed, click Send for Review.

    • Google will review the submission. If approved, the build is automatically published due to managed publishing being off.

Managing Internal Testing

For debug builds, which are used for internal testing before a broader release:

  1. Navigate to Internal Testing:

    • In the Release section, select Testing and then Internal testing.

  2. Create a New Release for Internal Testing:

    • Click on Create new release.

    • Upload your debug App Bundle by following similar steps as in the Production release.

  3. Specify Testers:

    • You can specify who can access this version by modifying the Pre-release Testers email list.

    • Add or confirm the emails of the testers who are authorized to install and test this build.

  4. Retrieve the Testing Link:

    • Once the release is saved and reviewed, retrieve the link provided by Google Play Console.

    • Share this link with the testers so they can directly download and install the app on their devices.

UI Components Documentation

Overview

The UI components of the PokerSnowie application are built using Jetpack Compose, which is Android's modern toolkit for building native UI. This section covers the various directories and their respective files within the com.pokersnowie.client.android package, providing detailed documentation to help new developers understand the structure and functionality of the UI components.

Understanding Jetpack Compose

Jetpack Compose is a modern toolkit for building native Android UI. It simplifies and accelerates UI development on Android with less code, powerful tools, and intuitive Kotlin APIs.

Key Concepts:

  1. Composable Functions:

    • Functions annotated with @Composable can describe UI elements.

    • These functions can be called from other composable functions, enabling the composition of complex UIs from smaller, reusable components.

    • Example:

      @Composable
      fun Greeting(name: String) {
          Text(text = "Hello, $name!")
      }
      
      @Preview
      @Composable
      fun PreviewGreeting() {
          Greeting("Android")
      }
      
    • @Preview allows you to preview the composable function in Android Studio without running the app.

  2. State Management:

    • Jetpack Compose uses state to manage and update the UI. The UI automatically updates when the state changes.

    • The remember function is used to hold state across recompositions.

    • The mutableStateOf function is used to create a mutable state.

      Copy code

      @Composable
      fun Counter() {
          var count by remember { mutableStateOf(0) }
          Button(onClick = { count++ }) {
              Text(text = "Count: $count")
          }
      }
      
  3. Layouts:

    • Compose provides a flexible way to build layouts using composable functions like Column, Row, Box, etc.

    • Example:

      @Composable
      fun LayoutExample() {
          Column {
              Text("First Item")
              Row {
                  Text("Second Item")
                  Spacer(modifier = Modifier.width(16.dp))
                  Text("Third Item")
              }
              Box {
                  Text("Fourth Item")
              }
          }
      }
      
  4. Theming:

    • Jetpack Compose allows for easy theming of the UI using MaterialTheme.

    • You can define colours, typography, and shapes in a theme and apply it across the app.

    • Example:

      @Composable
      fun ThemedApp() {
          MaterialTheme {
              Surface(color = MaterialTheme.colors.background) {
                  Greeting("Themed World")
              }
          }
      }
      
  5. Navigation:

    • Jetpack Compose provides a navigation library to handle in-app navigation.

    • NavHost and NavController are used to set up and manage navigation between different composable screens.

    • Example:

      @Composable
      fun NavigationExample() {
          val navController = rememberNavController()
          NavHost(navController, startDestination = "home") {
              composable("home") { HomeScreen(navController) }
              composable("details") { DetailsScreen(navController) }
          }
      }

Advanced Jetpack Compose Concepts

1. Coroutines and State Management:

Coroutines are used for asynchronous programming in Kotlin. In Jetpack Compose, coroutines can be used to manage state and perform background tasks.

  • LaunchedEffect: Runs a coroutine when the key(s) change and cancels it when the composition leaves the composition tree.

    Example:

    @Composable
    fun ExampleLaunchedEffect() {
        var count by remember { mutableStateOf(0) }
    
        LaunchedEffect(Unit) {
            while (true) {
                delay(1000L)
                count++
            }
        }
    
        Text(text = "Count: $count")
    }
  • SideEffect: Used for non-suspending side effects in Compose. It runs after every successful recomposition.

    Example:

    @Composable
    fun ExampleSideEffect(count: Int) {
        SideEffect {
            println("Current count: $count")
        }
        Text(text = "Count: $count")
    }

2. MutableState and StateFlow:

  • MutableState: Holds a single value and updates the UI when the value changes.

    Example:

    @Composable
    fun ExampleMutableState() {
        var text by remember { mutableStateOf("Hello") }
        Button(onClick = { text = "World" }) {
            Text(text)
        }
    }
    
  • StateFlow: A state-holder observable flow that emits the current and new state updates to its collectors.

    Example:

    class ExampleViewModel : ViewModel() {
        private val _uiState = MutableStateFlow("Hello")
        val uiState: StateFlow<String> = _uiState
    
        fun updateText(newText: String) {
            _uiState.value = newText
        }
    }
    
    @Composable
    fun ExampleStateFlow(viewModel: ExampleViewModel) {
        val text by viewModel.uiState.collectAsState()
        Button(onClick = { viewModel.updateText("World") }) {
            Text(text)
        }
    }

3. Remember and RememberSaveable:

  • remember: Used to store an object in the composition locally and retain it across recompositions.

    Example:

    @Composable
    fun ExampleRemember() {
        val count = remember { mutableStateOf(0) }
        Button(onClick = { count.value++ }) {
            Text("Count: ${count.value}")
        }
    }
    
  • rememberSaveable: Similar to remember but preserves the state across configuration changes (e.g., screen rotations).

    Example:

    @Composable
    fun ExampleRememberSaveable() {
        val count = rememberSaveable { mutableStateOf(0) }
        Button(onClick = { count.value++ }) {
            Text("Count: ${count.value}")
        }
    }
  1. Composable Functions and Performance:

  • CompositionLocal: Provides a way to propagate values down the compose tree without passing them as parameters.

    Example:

    val LocalExample = compositionLocalOf { "Default" }
    
    @Composable
    fun ExampleCompositionLocalProvider() {
        CompositionLocalProvider(LocalExample provides "Provided") {
            Text(LocalExample.current)
        }
    }
    
  • Recomposition: Compose only recomposes parts of the UI that have changed, making it highly efficient.

    Example:

    @Composable
    fun ExampleRecomposition() {
        var count by remember { mutableStateOf(0) }
        Button(onClick = { count++ }) {
            Text("Count: $count")
        }
    }

5. Consuming Events with MutableStateFlow:

  • Events in Compose: Use MutableStateFlow to handle one-time events such as showing the result of a socket function or navigating to another screen.

    Example:

    class EventViewModel : ViewModel() {
        private val _eventFlow = MutableStateFlow<Event?>(null)
        val eventFlow: StateFlow<Event?> = _eventFlow
    
        fun triggerEvent(event: Event) {
            _eventFlow.value = event
            _eventFlow.value = null // Reset event after consumption
        }
    }
    
    @Composable
    fun EventObserver(viewModel: EventViewModel) {
        val event by viewModel.eventFlow.collectAsState()
        event?.let {
            // Handle the event (e.g., show a toast)
        }
    }

Resources Directory

Overview

The res directory in an Android project contains all the non-code resources used by the application, such as images, fonts, sounds, and animation files. These resources are organized into subdirectories based on their types.

Subdirectories and Their Roles

  1. res/drawable: Contains image files (PNG, JPEG) and XML files for vector assets and other drawable resources.

  2. res/font: Contains custom font files used in the application.

  3. res/raw: Contains raw resource files such as sounds and JSON files for animations.

res/drawable

This directory holds various drawable resources, including images and XML files for vector graphics. These resources are used to display images and graphics in the application.

  • PNG/JPEG Files: Standard image files used for icons, backgrounds, and other graphical elements.

  • XML Files: Vector drawable resources defined using XML. These are scalable images often used for icons and other vector-based graphics.

res/font

This directory contains custom font files that the application uses for various text elements. These fonts enhance the visual appeal and readability of the text in the app.

res/raw

This directory holds raw resource files such as audio files and JSON files for animations. These files are used for various purposes within the application, such as playing sounds or displaying animations.

  • Audio Files: Used for sound effects, notifications, or background music.

  • JSON Files: Used for animations, particularly with libraries like Lottie for rendering vector animations.