Android

what is JOSE

What Is JOSE and Why It Matters for Financial Android Apps

In the age of mobile banking, digital wallets, and API-driven services, securing sensitive financial data is non-negotiable. Developers building financial Android applications face strict regulatory requirements and high user expectations for privacy and trust. One of the most widely adopted frameworks for securing JSON-based data exchanges is JOSE (Javascript Object Signing and Encryption).

This article explains what JOSE is, why it matters for financial applications — especially on Android — and how developers can leverage its standards to build secure, compliant, and user-trusted apps.

What Is JOSE?

JOSE (Javascript Object Signing and Encryption) is a suite of standards defined by the IETF (Internet Engineering Task Force). It provides a structured and interoperable way to secure JSON data, making it especially relevant for APIs, microservices, and mobile applications.

The JOSE framework consists of several core components:

  • JWS (JSON Web Signature): Ensures data integrity and authenticity by digitally signing JSON objects.
  • JWE (JSON Web Encryption): Protects sensitive data through encryption.
  • JWK (JSON Web Key): A standardized format for representing cryptographic keys.
  • JWA (JSON Web Algorithms): Defines which algorithms can be used for signing and encryption.
  • JWT (JSON Web Token): A compact, URL-safe way to transmit claims (e.g., identity or permissions).

These standards work together to secure communication channels, enforce authentication, and maintain data confidentiality across distributed systems.

Why JOSE Is Crucial for Financial Android Apps

1. Regulatory Compliance

Financial institutions and fintech startups must comply with frameworks like PCI-DSS, PSD2, and GDPR. JOSE provides the encryption, signatures, and secure key management needed to meet these regulatory requirements.

2. End-to-End Security

Financial Android apps rely on constant communication between client devices and backend servers. With JOSE, data is encrypted and signed before leaving the device, ensuring it cannot be intercepted or tampered with in transit.

3. Enhanced User Trust

In financial services, trust is currency. Users are more likely to adopt and remain loyal to apps that demonstrate strong data protection. JOSE offers transparent, standards-based security that boosts user confidence.

Real-World Use Cases in Financial Android Apps

  • Transaction Security: Protect payment and transfer data using JWE encryption.
  • User Authentication: Verify sessions and identities with JWT tokens signed by JWS.
  • API Communication: Use JOSE standards to enforce secure server-to-server and client-to-server communication.
  • Mobile Wallets & Banking Apps: Secure card details, balances, and sensitive personal data.

Best Practices for Developers Implementing JOSE

  • Always use strong algorithms from JWA (e.g., RS256 or ES256 for signing).
  • Rotate and manage JSON Web Keys (JWKs) securely.
  • Avoid storing sensitive tokens in plaintext on the Android device — use Android Keystore.
  • Implement short-lived JWTs with refresh tokens for better session security.
  • Validate signatures and claims on both client and server sides.

Frequently Asked Questions (FAQ)

Q1: Is JOSE the same as JWT?
 No. JWT (JSON Web Token) is just one standard within the JOSE framework. JOSE includes multiple standards like JWS, JWE, JWK, and JWA.

Q2: Why should I use JOSE instead of just HTTPS?
 HTTPS secures communication at the transport layer, but JOSE secures the actual payload data, ensuring protection even if HTTPS is terminated at proxies or gateways.

Q3: Which algorithms are best for financial Android apps?
 For signing, RS256 (RSA with SHA-256) and ES256 (Elliptic Curve with SHA-256) are recommended. For encryption, AES-GCM is a strong choice.

Q4: Can JOSE help with PSD2 and Open Banking compliance?
 Yes. Many Open Banking APIs rely on JWTs for secure claims and signed requests, making JOSE central to compliance strategies.

Q5: How do I store JOSE keys on Android securely?
 Use the Android Keystore System, which protects private keys in hardware-backed storage.

Conclusion

For developers building financial Android apps, JOSE isn’t optional — it’s essential. By combining encryption, signing, and key management under a standardized framework, JOSE makes it easier to secure sensitive data, comply with financial regulations, and earn user trust.

Implementing JOSE correctly not only strengthens your app’s security posture but also positions your product as a trustworthy solution in a competitive financial market.

Gradle Version Catalog in Android

Gradle Version Catalog in Android: A Complete Guide

Managing dependencies efficiently is crucial for any Android project, and Gradle Version Catalog makes it much easier. It centralizes dependency versions in one place, simplifies updates, and enhances project maintainability.

In this blog, we’ll explore:

  • What is Gradle Version Catalog?
  • Why should we use it?
  • How to implement it in an Android project with clear, step-by-step explanations.

Let’s dive in!

What is Gradle Version Catalog?

Gradle Version Catalog is a feature introduced in Gradle 7.0 that allows you to manage all your dependencies in a structured and centralized way using a file called libs.versions.toml.

Traditionally, we define dependencies in build.gradle or build.gradle.kts like this:

Kotlin
dependencies {
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
}

With Gradle Version Catalog, these versions are stored separately in a TOML file, making it easier to manage and update dependencies in large projects.

With Gradle Version Catalog

Dependencies are defined in gradle/libs.versions.toml:

Kotlin
[versions]
coreKtx = "1.7.0"
lifecycle = "2.4.0"

[libraries]
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }

And referenced in build.gradle.kts:

Kotlin
dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.runtime)
}

This keeps your project clean and scalable.

Why We Use Gradle Version Catalog?

Centralized Dependency Management: All dependencies and versions are stored in one place (libs.versions.toml), making maintenance easier.

Better Readability: Instead of scattered version numbers across multiple build.gradle files, you have a single version catalog for better readability.

Avoid Version Conflicts: Using a centralized catalog reduces inconsistencies and version mismatches in different modules.

Improved Consistency: Ensures that all modules use the same dependency versions.

Reduced Duplication: No need to repeatedly define dependencies in different module files.

Easier Updates: Updating dependencies is simpler since you only change the version in one file, and it reflects everywhere in the project.

Support for Plugins: Can also be used to manage Gradle plugins efficiently.

How to Set Up Gradle Version Catalog in an Android Project

If you’re using Gradle 8+, Version Catalog is enabled by default. For older versions (Gradle 7+), follow these steps:

1. Enable the Version Catalog

Inside settings.gradle (or settings.gradle.kts):

Kotlin
enableFeaturePreview("VERSION_CATALOGS")

2. Create the Version Catalog File

Inside your project root, create gradle/libs.versions.toml.

3. Define Versions and Dependencies

Example libs.versions.toml:

Kotlin
[versions]
kotlin = "1.8.20"
coreKtx = "1.9.0"

[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }

[plugins]
androidApplication = { id = "com.android.application", version = "8.0.0" }

4. Reference Dependencies in Build Scripts

Kotlin
plugins {
    id(libs.plugins.androidApplication.get().pluginId) version libs.plugins.androidApplication.get().version
    kotlin("android")
}

dependencies {
    implementation(libs.kotlin.stdlib)
    implementation(libs.androidx.core.ktx)
}

Best Practices for Using Gradle Version Catalog

  • Use version references instead of hardcoding values.
  • Group related dependencies logically in the TOML file.
  • Leverage aliases for clear naming conventions.
  • Keep libraries and plugins together for easier maintenance.
  • Regularly update dependencies via a single source of truth.

FAQs

Q1: What is the purpose of libs.versions.toml in Gradle?
 It centralizes all dependency versions in one place, making updates easier and preventing conflicts across modules.

Q2: Can Gradle Version Catalog manage plugins?
 Yes. You can declare both library dependencies and Gradle plugins in the TOML file.

Q3: Do I need Gradle 8 to use Version Catalog?
 No. It was introduced in Gradle 7.0. Gradle 8+ enables it by default, but you can enable it manually in Gradle 7 projects.

Q4: Is Gradle Version Catalog mandatory for Android projects?
 No, but it is highly recommended for scalability, especially in multi-module projects.

Q5: How does Gradle Version Catalog improve collaboration?
 By keeping all dependencies in one place, teams avoid mismatched versions across different modules or branches.

Conclusion

Gradle Version Catalog is a must-have tool for modern Android development. It reduces duplication, improves maintainability, and ensures consistent dependency management across projects.

If you’re still hardcoding dependencies in multiple build.gradle files, now is the perfect time to migrate. With libs.versions.toml, your Android project becomes cleaner, more maintainable, and easier to scale.

Dependency Management

Dependency Management in Android Gradle

Dependency management is a crucial aspect of Android development using Gradle. It helps in organizing external libraries, avoiding version conflicts, and improving project maintainability. In this blog, we will explore Gradle dependency management in Android, discuss best practices, and demonstrate its implementation with Kotlin code examples.

What is Dependency Management in Android?

In Android, applications rely on various third-party libraries, SDKs, and modules to add features without reinventing the wheel. These dependencies are managed using Gradle, a powerful build automation tool.

Gradle allows developers to:

  • Add dependencies from remote repositories like Maven Central or Google’s Maven.
  • Specify versions and update them easily.
  • Use dependency constraints to avoid conflicts.
  • Create reusable dependency configurations for modular projects.

Producers and Consumers in Dependency Management

In Android development, dependency management is about how libraries and modules interact. Simply put, it’s important to differentiate between producers and consumers in dependency management.

  • Producer: When you create an Android library (like a custom UI component or a utility library), you are the producer because you provide this library for others to use.
  • Consumer: When you add dependencies in your Android project (e.g., using implementation 'com.squareup.retrofit2:retrofit:2.9.0' in build.g
Kotlin
dependencies {
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
}

This simple line makes your project a consumer of Retrofit while Square (the creator) is the producer.

Understanding Gradle Dependencies in Android

Android projects use Gradle as a build system, and dependencies are added inside the build.gradle.kts (Kotlin DSL) or build.gradle (Groovy DSL) files.

Types of Dependencies in Android Gradle

Gradle lets you manage different types of dependencies, each useful for specific scenarios:

1. Local Dependencies

Include .jar or .aar files placed inside the libs/ folder:

Kotlin
dependencies {
    implementation(files("libs/mylibrary.jar"))
}

2. Remote Dependencies

Fetch external libraries from repositories like Maven Central, Google’s Maven, or JitPack:

Kotlin
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")
}

3. Project Dependencies

Link modules within the same Android project:

Kotlin
dependencies {
    implementation(project(":core"))
    implementation(project(":feature-login"))
}

Best Practices for Dependency Management

To keep your Gradle builds clean, stable, and efficient, follow these practices:

  • Use BOM (Bill of Materials): Align versions across related libraries.
  • Centralize versions: Store dependency versions in one place (e.g., gradle/libs.versions.toml or buildSrc).
  • Handle conflicts explicitly: Use dependencyResolutionStrategy or constraints.
  • Avoid duplicate libraries: Regularly check for unused dependencies.
  • Prefer api vs implementation wisely:
  • Use implementation for internal dependencies (faster builds).
  • Use api only when consumers need access to transitive dependencies.

Kotlin Example: Dependency Constraints

Here’s how you can enforce consistent versions across dependencies:

Kotlin
dependencies {
    constraints {
        implementation("com.squareup.okhttp3:okhttp:4.11.0") {
            because("Ensures compatibility across Retrofit and OkHttp usage")
        }
    }
}

This prevents Gradle from pulling in mismatched versions.

Conclusion

Dependency management in Android Gradle is more than just adding libraries — it’s about keeping your app maintainable, efficient, and conflict-free. By using BOMs, centralizing versions, managing conflicts, and understanding producers vs. consumers, you’ll avoid common pitfalls that slow down development.

Mastering Gradle dependency management not only improves build speed but also makes your project easier to scale and collaborate on. The payoff is an Android project that’s stable, consistent, and production-ready.

FAQ: Android Gradle Dependency Management

Q1: What’s the difference between implementation and api in Gradle?

  • implementation: Dependency is used internally; faster builds since it’s not exposed.
  • api: Exposes the dependency to consumers of your module. Use sparingly.

Q2: How do I avoid version conflicts in Gradle?
 Use dependency constraints, enforce consistent versions with a BOM, and run ./gradlew dependencies to audit conflicts.

Q3: Can I remove unused dependencies automatically?
 Yes, tools like Gradle Lint Plugin or IDE inspections can detect and remove unused libraries.

Q4: What’s the benefit of centralizing dependency versions?
 It ensures consistency across all modules, simplifies upgrades, and prevents subtle runtime issues from mismatched versions.

Q5: Should I prefer local or remote dependencies?
 Prefer remote dependencies from trusted repositories for maintainability. Use local JAR/AAR files only for custom or private libraries not available publicly.

Understanding and Declaring Gradle Dependencies in Android

Understanding and Declaring Gradle Dependencies in Android

Gradle is the official build system for Android development, offering flexibility and powerful dependency management. Understanding how to declare Gradle dependencies properly is crucial for efficient Android app development. In this guide, we’ll break down Gradle dependencies, their types, and how to use them effectively in Android Projects.

What are Gradle Dependencies?

Gradle dependencies are external libraries or modules that your Android project needs to function. They allow you to include reusable code, such as UI components, networking libraries, or database handlers, without writing everything from scratch.

Dependencies in Gradle are usually defined in the build.gradle.kts (Kotlin DSL) or build.gradle (Groovy) files. Modern Android projects use Kotlin DSL (build.gradle.kts), which is more type-safe and readable.

Producers and Consumers in Dependency Management

In Android development, dependency management is about how libraries and modules interact. Simply put, it’s important to differentiate between producers and consumers in dependency management.

  • Producer: When you create an Android library (like a custom UI component or a utility library), you are the producer because you provide this library for others to use.
  • Consumer: When you add dependencies in your Android project (e.g., using implementation 'com.squareup.retrofit2:retrofit:2.9.0' in build.gradle), you are the consumer because you are using a library created by someone else.

How to Declare Gradle Dependencies in Android Projects

Declaring dependencies correctly in Gradle is fundamental to building reliable Android applications. Dependencies are specified inside the Gradle build files, most commonly in build.gradle.kts (Kotlin DSL) or build.gradle (Groovy). Since modern Android development encourages using Kotlin DSL for better readability and type safety, this guide focuses on it.

Types of Gradle Dependencies

There are several types of dependencies in an Android project, each serving a different purpose:

  • implementation: The most common configuration, implementation adds the dependency to the project but hides it from consumers, improving build performance and avoiding unnecessary exposure.
  • api: Exposes the dependency to both the project and any consumers of the project’s library module. Useful when creating Android libraries.
  • compileOnly: Adds the dependency at compile time only; it is not packaged in the final APK. Ideal for annotation processors.
  • runtimeOnly: The dependency is available only at runtime but not at compile time.
  • testImplementation: Dependencies required only for testing purposes.
  • androidTestImplementation: Dependencies used only in Android instrumentation tests.

Adding a Dependency Example Using Kotlin DSL

Kotlin
dependencies {
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    api("androidx.core:core-ktx:1.7.0")
    testImplementation("junit:junit:4.13.2")
}

Using BOM for Version Management

To avoid version conflicts and manage library versions efficiently, Gradle supports the Bill of Materials (BOM). It declares a fixed set of compatible library versions.

Kotlin
dependencies {
    implementation(platform("com.google.firebase:firebase-bom:31.0.2"))
    implementation("com.google.firebase:firebase-analytics-ktx")
    implementation("com.google.firebase:firebase-auth-ktx")
}

Here, the versions for Firebase libraries are controlled centrally by the BOM, ensuring consistency without explicitly specifying versions for each artifact.

Best Practices for Dependency Management

  • Use Kotlin DSL: It improves readability, offers type safety, and better IDE support compared to Groovy syntax.
  • Keep dependencies updated: Regularly check for updates to benefit from bug fixes and performance improvements, but test carefully to avoid breaking changes.
  • Avoid unnecessary dependencies: Minimize the size of your APK and reduce build times by only including libraries essential to your app.
  • Utilize Dependency Constraints: To enforce or restrict specific versions across your entire project and avoid unexpected version conflicts.
  • Separate test dependencies: Keep your production code clean by isolating libraries used solely for testing.

Managing Transitive Dependencies

Gradle automatically includes transitive dependencies (dependencies of your dependencies). Sometimes, conflicts arise when different versions of the same library appear. Use strategies like excluding particular transitive dependencies or forcing specific versions to resolve conflicts.

Kotlin
dependencies {
    implementation("some.library:dependency:1.0.0") {
        exclude(group = "conflicting.group", module = "conflicting-module")
    }
}

Conclusion

Understanding how to declare and manage Gradle dependencies in Android development is essential for keeping projects well-organized and optimized. By leveraging Kotlin DSL, BOM, and dependency constraints, you can efficiently manage dependencies, prevent version conflicts, and ensure your project remains maintainable.

By following these best practices, you’ll improve your build process, reduce errors, and create a scalable Android application.

Module Dependencies

Module Dependencies in Android Gradle: A Complete Guide for Developers

Gradle is the backbone of Android development, powering build automation, dependency management, and project configuration. As projects scale, module dependencies in Android Gradle become essential for keeping your codebase organized, improving reusability, and reducing build times. In this guide, we’ll break down: What module dependencies are in Android Gradle Different types of dependencies (implementation, api,...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Gradle Dependencies Explained Choosing the Right Type for Your Android Project

Gradle Dependencies Explained: Choosing the Right Type for Your Android Project

If you’ve worked on an Android project, you’ve definitely dealt with Gradle dependencies. They help bring in external libraries, connect different parts of your project, and even let you add custom files. But not all dependencies work the same way. Some are used for linking modules, others for adding external projects, and some for including specific files. Choosing the right type can make your project more organized and easier to maintain.

In this blog, we’ll break down the different types of Gradle dependencies and when to use each one.

Types of Gradle dependencies

Gradle provides three main types of dependencies: 

  • Module dependencies
  • Project dependencies
  • File dependencies

Each type serves a different purpose, and choosing the right one ensures better project organization, maintainability, and performance.

Module Dependencies: The Standard Approach

Module dependencies are the most commonly used in Android development. They allow you to connect different modules within the same project.

Example use case:

  • You have a core module that handles networking and database logic.
  • Your app module depends on core to access those functionalities.

In Gradle, this might look like:

Kotlin
implementation project(":core")

Why use module dependencies?

  • Encourages modularization, making projects easier to scale.
  • Improves build times by allowing Gradle to compile modules separately.
  • Keeps your code organized and avoids duplication.

Project Dependencies: Linking External Projects

Project dependencies come into play when you want to include another Gradle project that isn’t part of your main project by default.

Example use case:

  • You’re working on a shared internal library that’s used across multiple apps.
  • Instead of publishing it to Maven or JCenter every time, you directly link the project.

In Gradle:

Kotlin
implementation project(path: ':shared-library')

Why use project dependencies?

  • Great for internal library development.
  • Lets you work with multiple projects simultaneously without extra publishing steps.
  • Useful in large teams or enterprise-level apps.

File Dependencies: Adding Custom JAR or AAR Files

File dependencies allow you to include JAR or AAR files directly into your project.

Example use case:

  • You’re integrating a third-party SDK that isn’t available in a public Maven repository.
  • You have a legacy .jar file you need for backward compatibility.

In Gradle:

Kotlin
implementation files('libs/custom-library.jar')

Why use file dependencies?

  • Perfect for custom or private libraries.
  • Helps when working with offline builds or older dependencies.

Best practice: Use file dependencies sparingly. If a library is available via Maven Central or Google’s repository, prefer that method — it’s easier to update and maintain.

Best Practices for Managing Gradle Dependencies

  • Prefer remote repositories (Maven Central, Google) over file dependencies.
  • Modularize your project: keep reusable logic in separate modules.
  • Use version catalogs (Gradle 7+) to centralize dependency versions.
  • Keep dependencies updated to avoid security vulnerabilities.
  • Avoid duplication by consolidating commonly used libraries in shared modules.

Conclusion

Gradle dependencies may seem simple, but choosing the right type — module, project, or file — can have a huge impact on your Android project’s structure and maintainability.

  • Use module dependencies for modular apps.
  • Use project dependencies for shared libraries across projects.
  • Use file dependencies only when necessary.

By understanding these distinctions, you’ll write cleaner code, speed up build times, and set yourself up for long-term project success.

FAQ: Gradle Dependencies in Android

Q1: What’s the difference between implementation and api in Gradle?

  • implementation: The dependency is only available in the current module.
  • api: The dependency is exposed to modules that depend on your module.

Q2: When should I use file dependencies in Gradle?

  • Only when the library isn’t available in a Maven or Gradle repository. Otherwise, prefer remote dependencies.

Q3: Can I convert a file dependency into a module or project dependency later?

  • Yes. If you gain access to the source code or publish the library internally, you can switch to module/project dependencies for better maintainability.

Q4: Do Gradle dependencies affect build speed?

  • Yes. Modular dependencies can improve build times, while excessive file dependencies can slow things down.
State Hoisting in Jetpack Compose

State Hoisting in Jetpack Compose: Best Practices for Scalable Apps

When building Android apps with Jetpack Compose, state management is one of the most important pieces to get right. If you don’t handle state properly, your UI can become messy, tightly coupled, and hard to scale. That’s where State Hoisting in Jetpack Compose comes in.

In this post, we’ll break down what state hoisting is, why it matters, and how you can apply best practices to make your Compose apps scalable, maintainable, and easy to debug.

What Is State Hoisting in Jetpack Compose?

In simple terms, state hoisting is the process of moving state up from a child composable into its parent. Instead of a UI component directly owning and mutating its state, the parent holds the state and passes it down, while the child only receives data and exposes events.

This separation ensures:

  • Reusability: Components stay stateless and reusable.
  • Single Source of Truth: State is managed in one place, reducing bugs.
  • Scalability: Complex UIs are easier to extend and test.

A Basic Example of State Hoisting

Let’s say you have a simple text field. Without state hoisting, the child manages its own state like this:

Kotlin
@Composable
fun SimpleTextField() {
    var text by remember { mutableStateOf("") }

    TextField(
        value = text,
        onValueChange = { text = it }
    )
}

This works fine for small apps, but the parent composable has no control over the value. It becomes difficult to coordinate multiple composables.

Now let’s apply state hoisting:

Kotlin
@Composable
fun SimpleTextField(
    text: String,
    onTextChange: (String) -> Unit
) {
    TextField(
        value = text,
        onValueChange = onTextChange
    )
}

And in the parent:

Kotlin
@Composable
fun ParentComposable() {
    var text by remember { mutableStateOf("") }

    SimpleTextField(
        text = text,
        onTextChange = { text = it }
    )
}

Here’s what changed

  • The parent owns the state (text).
  • The child only displays the state and sends updates back via onTextChange.

This is the core idea of State Hoisting in Jetpack Compose.

Why State Hoisting Matters for Scalable Apps

As your app grows, different UI elements will need to communicate. If each composable owns its own state, you’ll end up duplicating data or creating inconsistencies.

By hoisting state:

  • You centralize control, making it easier to debug.
  • You avoid unexpected side effects caused by hidden internal state.
  • You enable testing, since state management is separated from UI rendering.

Best Practices for State Hoisting in Jetpack Compose

1. Keep Composables Stateless When Possible

A good rule of thumb: UI elements should be stateless and only care about how data is displayed. The parent decides what data to provide.

Example: A button shouldn’t decide what happens when it’s clicked — it should simply expose an onClick callback.

2. Use remember Wisely in Parents

State is usually managed at the parent level using remember or rememberSaveable.

  • Use remember when state only needs to survive recomposition.
  • Use rememberSaveable when you want state to survive configuration changes (like screen rotations).
Kotlin
var text by rememberSaveable { mutableStateOf("") }

3. Follow the Unidirectional Data Flow Pattern

Compose encourages Unidirectional Data Flow (UDF):

  1. Parent owns state.
  2. State is passed down to child.
  3. Child emits events back to parent.

This clear flow makes apps predictable and avoids infinite loops or messy side effects.

4. Keep State Close to Where It’s Used, But Not Too Close

Don’t hoist all state to the top-level of your app. That creates unnecessary complexity. Instead, hoist it just far enough up so that all dependent composables can access it.

For example, if only one screen needs a piece of state, keep it inside that screen’s parent composable rather than in the MainActivity.

5. Use ViewModels for Shared State Across Screens

For larger apps, when multiple screens or composables need the same state, use a ViewModel.

Kotlin
class LoginViewModel : ViewModel() {
    var username by mutableStateOf("")
        private set

    fun updateUsername(newValue: String) {
        username = newValue
    }
}

Then in your composable:

Kotlin
@Composable
fun LoginScreen(viewModel: LoginViewModel = viewModel()) {
    SimpleTextField(
        text = viewModel.username,
        onTextChange = { viewModel.updateUsername(it) }
    )
}

This pattern keeps your UI clean and separates business logic from presentation.

Common Mistakes to Avoid

  • Keeping state inside deeply nested children: This makes it impossible to share or control at higher levels.
  • Over-hoisting: Don’t hoist state unnecessarily if no other composable needs it.
  • Mixing UI logic with business logic: Keep state handling in ViewModels where appropriate.

Conclusion

State Hoisting in Jetpack Compose is more than just a coding pattern — it’s the backbone of building scalable, maintainable apps. By lifting state up, following unidirectional data flow, and keeping components stateless, you set yourself up for long-term success.

To summarize:

  • Keep state in the parent, not the child.
  • Pass data down, send events up.
  • Use ViewModels for shared or complex state.

By applying these best practices, you’ll build apps that are not only functional today but also easy to scale tomorrow.

Shared ViewModels in Android

Understanding Shared ViewModels in Android: A Comprehensive Guide

In modern Android development, ViewModel has become an indispensable component for managing UI-related data in a lifecycle-conscious manner. One powerful application of ViewModels is sharing data between multiple fragments or activities. This guide provides a deep dive into shared ViewModels, explaining their purpose, implementation, and best practices for creating seamless data sharing in your Android apps.

The Concept of Shared ViewModels

A Shared ViewModel is a ViewModel instance that is accessible across multiple fragments or activities, enabling shared state management. This approach is ideal when:

  • Fragment Communication: Multiple fragments need to work with the same data, such as a user profile or settings.
  • Decoupling Logic: You want fragments to exchange information without creating brittle, tightly-coupled dependencies.
  • Navigation Component Scenarios: Sharing data across destinations within a navigation graph requires clean state management.

Unlike standalone ViewModels scoped to a single UI component, shared ViewModels can be scoped to an entire activity or a specific navigation graph, allowing seamless state sharing while respecting lifecycle boundaries.

Why Use Shared ViewModels?

Here are some compelling reasons to choose shared ViewModels:

  1. Lifecycle Safety: Data stored in a ViewModel persists through configuration changes like screen rotations, avoiding unwanted resets.
  2. Simplified Communication: Fragments don’t need to interact directly, reducing the risk of complex dependencies and bugs.
  3. Consistent Data: A single source of truth ensures data integrity and synchronization across multiple components.
  4. Modern Architecture: Shared ViewModels align perfectly with MVVM (Model-View-ViewModel) architecture, a best practice for building scalable Android apps.

Step-by-Step Implementation of Shared ViewModels

Setting Up Dependencies

Add the core libraries you’ll need (use the latest stable versions from AndroidX/Hilt):

Kotlin
// app/build.gradle.kts
dependencies {
    // Fragments & Activity KTX
    implementation("androidx.fragment:fragment-ktx:<ver>")
    implementation("androidx.activity:activity-ktx:<ver>")

    // Lifecycle / ViewModel / coroutines support
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:<ver>")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:<ver>")

    // Only if you still use LiveData:
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:<ver>")

    // Jetpack Navigation (Fragment)
    implementation("androidx.navigation:navigation-fragment-ktx:<ver>")
    implementation("androidx.navigation:navigation-ui-ktx:<ver>")

    // (Optional) Hilt for DI + ViewModels
    implementation("com.google.dagger:hilt-android:<ver>")
    kapt("com.google.dagger:hilt-android-compiler:<ver>")
    implementation("androidx.hilt:hilt-navigation-fragment:<ver>")
    kapt("androidx.hilt:hilt-compiler:<ver>")
}

If using Hilt, also apply the plugin in your module’s Gradle file:

Kotlin
plugins {
    id("com.google.dagger.hilt.android")
    kotlin("kapt")
}

Designing the Shared ViewModel

Prefer a single source of UI state with immutable data classes and expose it via StateFlow. Keep side effects (like toasts or navigation) separate using a SharedFlow for one-off events.

Kotlin
// Shared ViewModel example (Kotlin)
@HiltViewModel // Remove if you’re not using Hilt
class ProfileSharedViewModel @Inject constructor(
    private val repo: ProfileRepository,            // Your data source
    private val savedStateHandle: SavedStateHandle  // For process death & args
) : ViewModel() {

    data class UiState(
        val user: User? = null,
        val isLoading: Boolean = false,
        val error: String? = null
    )

    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState

    // One-off events (navigation, snackbar, etc.)
    private val _events = MutableSharedFlow<Event>()
    val events: SharedFlow<Event> = _events

    sealed interface Event { object Saved : Event }

    fun load(userId: String) {
        // Example of persisting inputs using SavedStateHandle
        savedStateHandle["lastUserId"] = userId

        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true, error = null) }
            runCatching { repo.fetchUser(userId) }
                .onSuccess { user -> _uiState.update { it.copy(user = user, isLoading = false) } }
                .onFailure { e -> _uiState.update { it.copy(isLoading = false, error = e.message) } }
        }
    }

    fun updateName(newName: String) {
        _uiState.update { state ->
            state.copy(user = state.user?.copy(name = newName))
        }
    }

    fun save() {
        val current = _uiState.value.user ?: return
        viewModelScope.launch {
            runCatching { repo.saveUser(current) }
                .onSuccess { _events.emit(Event.Saved) }
                .onFailure { e -> _uiState.update { it.copy(error = e.message) } }
        }
    }
}

SavedStateHandle survives process death when used with Navigation and lets you read nav arguments via savedStateHandle.get<T>("arg") or create StateFlows: savedStateHandle.getStateFlow("key", default).

Scoping Options (Activity vs. Nav Graph)

Activity scope — share across all fragments in the same activity:

Kotlin
private val vm: ProfileSharedViewModel by activityViewModels()
  • Lives as long as the Activity is alive (across configuration changes).
  • Good for app-wide state within that activity (e.g., cart, session, toolbar state).

Navigation graph scope — share only within a specific flow:

Kotlin
private val vm: ProfileSharedViewModel by navGraphViewModels(R.id.profile_graph)
  • One instance per NavBackStackEntry for that graph.
  • Cleared when that graph is popped off the back stack.
  • Great for multi-step wizards (e.g., signup → verify → done).

Using Hilt? Get a Hilt-injected, nav-graph–scoped VM with:

Kotlin
private val vm: ProfileSharedViewModel by hiltNavGraphViewModels(R.id.profile_graph)

Avoid sharing a ViewModel across different activities. Use a repository/single source of truth instead, or adopt a single-activity architecture.

Using the Shared ViewModel in Fragments

Collect state with lifecycle awareness. Use repeatOnLifecycle so collection stops when the view is not visible.

Kotlin
@AndroidEntryPoint // if using Hilt
class EditProfileFragment : Fragment(R.layout.fragment_edit_profile) {

    private val vm: ProfileSharedViewModel by activityViewModels() // or navGraphViewModels(...)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // State
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                vm.uiState.collect { state ->
                    // update text fields, progress bars, errors
                }
            }
        }

        // One-off events
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                vm.events.collect { event ->
                    when (event) {
                        is ProfileSharedViewModel.Event.Saved -> {
                            // e.g., findNavController().navigateUp()
                        }
                    }
                }
            }
        }

        // Example inputs
        val save = view.findViewById<Button>(R.id.saveButton)
        save.setOnClickListener { vm.save() }
    }
}

And another fragment in the same scope sees the same instance:

Kotlin
class PreviewProfileFragment : Fragment(R.layout.fragment_preview_profile) {
    private val vm: ProfileSharedViewModel by activityViewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                vm.uiState.collect { state ->
                    // render preview using state.user
                }
            }
        }
    }
}

If you prefer LiveData:

Kotlin
vm.liveData.observe(viewLifecycleOwner) { state -> /* ... */ }

Passing Arguments & Using SavedStateHandle

When you navigate with arguments, Navigation stores them in the destination’s SavedStateHandle, which the ViewModel can read:

Kotlin
// In the ViewModel (constructor already has savedStateHandle)
private val userId: String? = savedStateHandle["userId"]

init {
    userId?.let(::load)
}

You can also write to the handle to restore after process death:

Kotlin
savedStateHandle["draftName"] = "Amol"
val draftNameFlow = savedStateHandle.getStateFlow("draftName", "")

Handling One-Off Events Correctly

Never mix events with state (or they re-trigger on rotation). Use SharedFlow (or Channel) for fire-and-forget actions:

Kotlin
private val _events = MutableSharedFlow<Event>(extraBufferCapacity = 1)
val events = _events.asSharedFlow()

// Emit: _events.tryEmit(Event.Saved)

Testing a Shared ViewModel

Use the coroutine test utilities and a fake repository:

Kotlin
@OptIn(ExperimentalCoroutinesApi::class)
class ProfileSharedViewModelTest {

    @get:Rule
    val mainDispatcherRule = MainDispatcherRule() // sets Dispatchers.Main to a TestDispatcher

    private val repo = FakeProfileRepository()
    private lateinit var vm: ProfileSharedViewModel

    @Before 
    fun setUp() {
        vm = ProfileSharedViewModel(repo, SavedStateHandle())
    }

    @Test 
    fun `load populates user and clears loading`() = runTest {
        vm.load("42")
        val state = vm.uiState.first { !it.isLoading }
        assertEquals("42", state.user?.id)
        assertNull(state.error)
    }
}

Implement MainDispatcherRule by swapping Dispatchers.Main with a StandardTestDispatcher. Keep repositories pure and synchronous in tests, or use runTest with fakes.

When to Choose Each Scope

Use Activity scope when:

  • Tabs/bottom navigation fragments need the same state.
  • Data lives for the whole activity session (e.g., cart, auth session).

Use Nav-graph scope when:

  • Data is local to a flow (onboarding, multi-step form).
  • You want the ViewModel cleared when the flow finishes (pop).

Best Practices

  • Expose immutable state (StateFlow, LiveData) and keep mutables private.
  • Don’t hold views/context inside ViewModels. Inject repositories/use cases instead.
  • Use viewLifecycleOwner when observing in fragments (not this), to avoid leaks.
  • Keep UI state small & serializable if you rely on SavedStateHandle.
  • Model errors in state and display them; don’t throw them up to the UI.
  • Avoid shared ViewModels across activities; share via repository or a data layer.
  • Prefer StateFlow for new code; LiveData is still fine if your app already uses it.

Common Pitfalls (and Fixes)

  • State replays on rotation (toast fires again): Use SharedFlow/Channel for events, not StateFlow/LiveData.
  • ViewModel not shared between fragments: Ensure both fragments use the same scope (activityViewModels() or the same navGraphId).
  • ViewModel survives too long: You probably used activity scope where a nav-graph scope made more sense.
  • Collectors keep running off-screen: Wrap collect in repeatOnLifecycle(Lifecycle.State.STARTED).

Minimal, End-to-End Example

Navigation graph (excerpt):

XML
<!-- res/navigation/profile_graph.xml -->
<navigation
    android:id="@+id/profile_graph"
    app:startDestination="@id/editProfileFragment">

    <fragment
        android:id="@+id/editProfileFragment"
        android:name="com.example.EditProfileFragment">
        <action
            android:id="@+id/action_edit_to_preview"
            app:destination="@id/previewProfileFragment" />
        <argument
            android:name="userId"
            app:argType="string" />
    </fragment>

    <fragment
        android:id="@+id/previewProfileFragment"
        android:name="com.example.PreviewProfileFragment" />
</navigation>

Fragments sharing the same ViewModel via nav graph:

Kotlin
class EditProfileFragment : Fragment(R.layout.fragment_edit_profile) {
    private val vm: ProfileSharedViewModel by navGraphViewModels(R.id.profile_graph)
    // collect uiState/events as shown earlier…
}

class PreviewProfileFragment : Fragment(R.layout.fragment_preview_profile) {
    private val vm: ProfileSharedViewModel by navGraphViewModels(R.id.profile_graph)
    // collect uiState and render preview…
}

Conclusion

Shared ViewModels let fragments share state safely without talking to each other directly. Scope them to the activity for app-wide state or to a navigation graph for flow-scoped state. Expose state with StateFlow, drive UI with lifecycle-aware collectors, use SavedStateHandle for resilience, and keep one-off events separate. Follow these patterns and you’ll get predictable, testable, and decoupled UI flows.

State Management in Jetpack Compose

Mastering State Management in Jetpack Compose: A Comprehensive Guide

State management is one of the most critical aspects of building dynamic and interactive Android applications. With Jetpack Compose, Android’s modern UI toolkit, managing state becomes more intuitive, but it also introduces new paradigms that developers need to understand. In this blog, we’ll explore state management in Jetpack Compose in detail. We’ll break down essential...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Digital Signature

What Is a Digital Signature & How SSL Certificates Work on Android Devices

In today’s digital world, security and trust are essential, especially when it comes to sensitive information exchanged over the internet. Two foundational technologies that play a critical role in ensuring online security are digital signatures and SSL certificates. If you’re an Android user or developer, understanding these concepts is crucial for protecting your data and securing communications. 

This blog will explain what a digital signature is, how SSL certificates work on Android devices, and their importance.

What Is a Digital Signature?

A digital signature is a kind of electronic fingerprint — a unique code attached to digital documents or messages that proves their authenticity and integrity. Think of it like a handwritten signature but much more secure because it’s based on cryptography.

Why Are Digital Signatures Important?

  • Authentication: Verifies the sender’s identity.
  • Integrity: Ensures the message or document has not been altered after signing.
  • Non-repudiation: The sender cannot deny having sent the message.

Digital signatures use a pair of keys: a private key (known only to the signer) and a public key (shared with others). When you sign a document, your device uses your private key to create a unique signature. Others can use your public key to verify that signature’s authenticity.

How Digital Signatures Work

Let’s look at a simplified workflow using cryptographic functions in Android’s Java/Kotlin environment to understand the digital signature process.

Kotlin
// Generating a digital signature in Android using Java

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;

public class DigitalSignatureExample {

    public static void main(String[] args) throws Exception {

        // Step 1: Generate key pair (public and private keys)
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(2048);
        KeyPair pair = keyGen.generateKeyPair();
        PrivateKey privateKey = pair.getPrivate();
        PublicKey publicKey = pair.getPublic();

        // Step 2: Sign data
        String data = "This is a message to sign";
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(data.getBytes());
        byte[] digitalSignature = signature.sign();

        // Step 3: Verify signature
        Signature verifier = Signature.getInstance("SHA256withRSA");
        verifier.initVerify(publicKey);
        verifier.update(data.getBytes());
        boolean isVerified = verifier.verify(digitalSignature);
        System.out.println("Signature Verified: " + isVerified);
    }
}

Here,

  • Step 1: We create a key pair using RSA, a popular cryptographic algorithm.
  • Step 2: Using the private key, we “sign” the data. The process hashes the data and encrypts it with the private key to create the digital signature.
  • Step 3: Anyone with the matching public key can verify the signature. They hash the original data and decrypt the signature to confirm both match, ensuring the data is authentic and untampered.

What Are SSL Certificates?

An SSL (Secure Sockets Layer) certificate is a digital certificate that authenticates a website’s identity and enables an encrypted connection. When you visit a website with HTTPS, the SSL certificate is what makes the communication between your device (like an Android phone) and the website secure.

Key Features of SSL Certificates

  • Encryption: They encrypt data sent between your browser and the web server.
  • Authentication: They confirm the website’s identity using a digital signature issued by a trusted Certificate Authority (CA).
  • Data Integrity: They ensure data is not altered during transmission.

How SSL Certificates Work on Android Devices

When your Android device connects to an HTTPS website, a process called the SSL/TLS handshake happens. This is a behind-the-scenes conversation between your device and the web server to establish a secure encrypted connection.

The SSL/TLS Handshake Steps Simplified

1. Client Hello: Your Android device sends a request to the server saying it wants to connect securely, including which encryption methods it supports.

2. Server Hello & Certificate: The server responds with its SSL certificate, which contains its public key and the digital signature from a CA to prove authenticity.

3. Verification: Your Android device verifies the certificate by checking:

  • Is the certificate issued by a trusted CA (Android maintains a list of trusted root certificates)?
  • Is the certificate valid and not expired or revoked?
  • Does the domain match the certificate?

4. Session Key Creation: Once verified, your device and the server create a shared secret key for encrypting data during the session.

5. Secure Communication: All data transferred is encrypted with this session key, keeping your information safe from eavesdroppers.

Why Are Digital Signatures Integral to SSL Certificates?

The digital signature within an SSL certificate is created by a trusted Certificate Authority (CA). This signature vouches for the authenticity of the certificate, confirming the server’s identity. Without this digital signature, an SSL certificate wouldn’t be trustworthy, and your Android device couldn’t be sure it’s communicating with the intended server.

Why You Should Care About Digital Signatures & SSL on Android

  • Digital signatures are essential for verifying identity and data integrity.
  • SSL certificates use digital signatures to secure websites.
  • Android devices use SSL certificates to ensure safe browsing and protect user data.
  • Developers should understand how to implement and verify digital signatures to build secure Android apps.

By grasping these concepts, you empower yourself to better protect your digital life, whether you’re surfing the web or developing mobile apps.

error: Content is protected !!