CountryCodePickerCompose

CountryCodePickerCompose

A Modern Country Code Picker for Jetpack Compose

platform API JitPack GitHub license

GitHub issues GitHub forks GitHub stars GitHub contributors

[!NOTE] Full tutorial on this library on YouTube. You can check it out here

πŸŽ‰ What’s New in v0.2

Version 0.2 brings major UI/UX improvements along with critical bug fixes and enhanced stability:

🎨 Material 3 Design Overhaul

πŸ› Critical Bug Fixes

πŸ†™ Updated Dependencies

πŸ“± Enhanced Sample App

πŸ›‘οΈ Better Development Experience

Migration from 0.1.x: No breaking changes! Just update your dependencies. See the Migration Guide below.

Table of Contents

Purpose

In various apps, we need to use a country code picker. There are several libraries available for this purpose. In XML we have a CCP created by hbb20, which is great. In Jetpack Compose there are few libraries but they lack some features and updates. So, I created this library to use in Jetpack Compose.

CountryCodePickerCompose is:

Features

Demo

Different Use Cases Country Picker Dialog Picker Bottom Sheet
Shot1 Shot2 Shot3
Shot4 Shot5 Shot6

You can download the test apk to try out all features - Download APK

Prerequisites

Kotlin DSL

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven {
            url = uri("https://jitpack.io")
        }
    }
}

Dependency

Add this to your module’s build.gradle.kts file (latest version JitPack):

Kotlin DSL

dependencies {
    implementation("com.github.ahmmedrejowan:CountryCodePickerCompose:0.2")
}

Usage

There are 4 different ways to use this library:

  1. CountryCodePicker - Regular composable (can be used anywhere, attached to TextField)
  2. CountryCodePickerTextField - All-in-one TextField with integrated picker
  3. CountryPickerDialog - Standalone picker dialog
  4. CountryPickerBottomSheet - Material 3 bottom sheet picker

CountryCodePicker

A flexible composable that can be used in multiple ways - as a standalone picker, attached to a TextField, or in custom layouts.

Basic Usage:

var selectedCountry by remember { mutableStateOf(Country.UnitedStates) }

CountryCodePicker(
    selectedCountry = selectedCountry,
    onCountrySelected = { selectedCountry = it },
    modifier = Modifier.fillMaxWidth()
)

Compact Mode (Flag + Code Only):

CountryCodePicker(
    selectedCountry = selectedCountry,
    onCountrySelected = { selectedCountry = it },
    viewCustomization = ViewCustomization(
        showFlag = true,
        showCountryIso = false,
        showCountryName = false,
        showCountryCode = true,
        showArrow = true
    ),
    showSheet = true  // Use bottom sheet (false for dialog)
)

Key Parameters:

CountryCodePickerTextField

An all-in-one solution with integrated TextField, validation, and formatting.

Basic Usage:

var phoneNumber by remember { mutableStateOf("") }
var selectedCountry by remember { mutableStateOf(Country.UnitedStates) }

CountryCodePickerTextField(
    number = phoneNumber,
    onValueChange = { country, number, isValid ->
        selectedCountry = country
        phoneNumber = number
    },
    selectedCountry = selectedCountry,
    modifier = Modifier.fillMaxWidth(),
    label = { Text("Phone Number") },
    showError = true  // Show validation errors
)

With Custom Styling:

CountryCodePickerTextField(
    number = phoneNumber,
    onValueChange = { country, number, isValid ->
        selectedCountry = country
        phoneNumber = number
    },
    selectedCountry = selectedCountry,
    modifier = Modifier.fillMaxWidth(),
    shape = RoundedCornerShape(16.dp),
    textStyle = MaterialTheme.typography.bodyLarge,
    label = { Text("Enter Phone Number") },
    trailingIcon = {
        if (phoneNumber.isNotEmpty()) {
            IconButton(onClick = { phoneNumber = "" }) {
                Icon(Icons.Default.Clear, "Clear")
            }
        }
    },
    showError = true,
    showSheet = true
)

Key Features:

CountryPickerDialog

A standalone picker dialog for manual integration.

Usage:

var selectedCountry by remember { mutableStateOf(Country.UnitedStates) }
var isDialogOpen by remember { mutableStateOf(false) }

// Your UI element to trigger the dialog
Button(onClick = { isDialogOpen = true }) {
    Text("Select Country")
}

// Dialog
if (isDialogOpen) {
    CountryPickerDialog(
        onDismissRequest = { isDialogOpen = false },
        onItemClicked = { country ->
            selectedCountry = country
            isDialogOpen = false
        },
        selectedCountry = selectedCountry,
        pickerCustomization = PickerCustomization(
            showCountryCode = true,
            showFlag = true
        )
    )
}

CountryPickerBottomSheet

A Material 3 bottom sheet picker with proper drag handle and keyboard support.

Usage:

var selectedCountry by remember { mutableStateOf(Country.UnitedStates) }
var isSheetOpen by remember { mutableStateOf(false) }

// Your UI element to trigger the sheet
Button(onClick = { isSheetOpen = true }) {
    Text("Select Country")
}

// Bottom Sheet
if (isSheetOpen) {
    CountryPickerBottomSheet(
        onDismissRequest = { isSheetOpen = false },
        onItemClicked = { country ->
            selectedCountry = country
            isSheetOpen = false
        },
        selectedCountry = selectedCountry,
        pickerCustomization = PickerCustomization(
            showCountryCode = true,
            showFlag = true
        )
    )
}

Features:

Customization

ViewCustomization

Customize how the picker button/view appears:

data class ViewCustomization(
    var showFlag: Boolean = true,          // Show country flag emoji
    var showCountryIso: Boolean = false,   // Show country ISO code (e.g., "US")
    var showCountryName: Boolean = false,  // Show country name
    var showCountryCode: Boolean = true,   // Show country code (e.g., "+1")
    var showArrow: Boolean = true,         // Show dropdown arrow
    var clipToFull: Boolean = false        // Clip content to prevent overflow
)

Examples:

// Full display: Flag + Name + (ISO) + Code
ViewCustomization(
    showFlag = true,
    showCountryIso = true,
    showCountryName = true,
    showCountryCode = true
)

// Compact: Flag + Code only
ViewCustomization(
    showFlag = true,
    showCountryCode = true,
    showCountryName = false,
    showCountryIso = false
)

// Minimal: Code only
ViewCustomization(
    showFlag = false,
    showCountryCode = true,
    showArrow = true
)

PickerCustomization

Customize the picker dialog/bottom sheet appearance:

data class PickerCustomization(
    var itemPadding: Int = 10,                           // Padding for list items
    var showSearchClearIcon: Boolean = true,             // Show clear button in search
    var showCountryCode: Boolean = true,                 // Show codes in list
    var showFlag: Boolean = true,                        // Show flags in list
    var showCountryIso: Boolean = false,                 // Show ISO codes in list

    // Custom text strings (can use string resources or direct strings)
    var headerTitle: Int = R.string.select_country,      // Dialog/sheet header
    var headerTitleText: String? = null,                 // Or use direct string
    var searchHint: Int = R.string.search_country,       // Search field hint
    var searchHintText: String? = null                   // Or use direct string
)

Examples:

// Custom strings for localization
PickerCustomization(
    headerTitleText = "SΓ©lectionner un pays",  // French
    searchHintText = "Rechercher",
    showCountryCode = true
)

// Minimal picker with codes only
PickerCustomization(
    showFlag = false,
    showCountryCode = true,
    showCountryIso = true
)

Utility Functions

Automatic Country Detection

Automatically detect the user’s country based on their device settings:

var selectedCountry by remember { mutableStateOf(Country.UnitedStates) }

// Detect country on first composition
LaunchedEffect(Unit) {
    CCPUtils.getCountryAutomatically(context = LocalContext.current)?.let {
        selectedCountry = it
    }
}

// Or use in non-preview mode
if (!LocalInspectionMode.current) {
    CCPUtils.getCountryAutomatically(context = LocalContext.current)?.let {
        selectedCountry = it
    }
}

How it works:

  1. First tries to detect from SIM card (requires READ_PHONE_STATE permission)
  2. Falls back to device locale if SIM detection fails
  3. Returns null if country cannot be determined

Phone Number Validation

Validate phone numbers using Google’s libphonenumber library:

val context = LocalContext.current
val validator = remember(context) { CCPValidator(context = context) }

var phoneNumber by remember { mutableStateOf("") }
var isValid by remember { mutableStateOf(false) }

OutlinedTextField(
    value = phoneNumber,
    onValueChange = { newNumber ->
        phoneNumber = newNumber
        isValid = validator(
            number = newNumber,
            countryCode = selectedCountry.countryCode
        )
    },
    isError = phoneNumber.isNotEmpty() && !isValid,
    label = { Text(if (isValid) "Valid βœ“" else "Phone Number") }
)

Features:

Visual Transformation

Automatically format phone numbers as the user types:

OutlinedTextField(
    value = phoneNumber,
    onValueChange = { phoneNumber = it },
    visualTransformation = CCPTransformer(
        context = LocalContext.current,
        countryIso = selectedCountry.countryIso
    ),
    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone)
)

What it does:

Note: This is already integrated in CountryCodePickerTextField.

Find countries by phone number:

val context = LocalContext.current
var searchQuery by remember { mutableStateOf("") }
var foundCountry by remember { mutableStateOf<Country?>(null) }

OutlinedTextField(
    value = searchQuery,
    onValueChange = { query ->
        searchQuery = query
        // Find country from phone number
        foundCountry = if (query.startsWith("+")) {
            Country.findCountry(query, context)
        } else null
    },
    label = { Text("Enter phone number (e.g., +12125551234)") }
)

// Display result
foundCountry?.let { country ->
    Text("Country: ${country.countryName} (${country.countryCode})")
}

Features:

πŸ“¦ Migration from 0.1 to 0.2

Good news! Version 0.2 has NO breaking changes. Your existing code will continue to work without modifications.

What You Need to Update

1. Update Dependencies:

// In your build.gradle.kts (Project level)
plugins {
    id("org.jetbrains.kotlin.plugin.compose") version "2.1.0" apply false
}

// In your build.gradle.kts (Module level)
plugins {
    id("org.jetbrains.kotlin.plugin.compose")
}

dependencies {
    // Update the library version
    implementation("com.github.ahmmedrejowan:CountryCodePickerCompose:0.2")
}

2. Update Kotlin and Compose (Recommended):

// In your gradle/libs.versions.toml or build.gradle.kts
kotlin = "2.1.0"                      // Updated from 1.9.0
composeBom = "2024.12.01"             // Updated from 2024.05.00

3. Remove Old Compiler Options (if you have them):

// REMOVE this from your build.gradle.kts:
android {
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.x"  // Not needed with Kotlin 2.0+
    }
}

What Works Automatically

βœ… All your existing code - No API changes required βœ… Your custom styles - ViewCustomization and PickerCustomization remain the same βœ… Your callbacks - onCountrySelected, onValueChange work exactly as before βœ… ProGuard/R8 - Rules are now automatically applied via consumer-rules.pro

What’s New (Optional to Use)

πŸ†• Material 3 Improvements - Your UI automatically looks better with no changes πŸ†• Better keyboard handling - Bottom sheets work better with keyboard (automatic) πŸ†• 19 Languages - Country names support multiple languages (automatic) πŸ†• Phone number search - Use Country.findCountry(phoneNumber) to find country from phone number πŸ†• Enhanced validation - Better error handling (automatic)

Testing Your Migration

  1. Update dependencies
  2. Clean and rebuild: ./gradlew clean build
  3. Test your app - everything should work as before
  4. Optional: Update to use new features if desired

Need Help?

If you encounter any issues during migration:

  1. Check the Changelog for detailed changes
  2. See the Sample App for updated examples
  3. Open an issue on GitHub

Sample App Examples

The included sample app demonstrates 7 comprehensive usage patterns with 15+ examples:

  1. Basic Picker Variants - Full display, compact, minimal configurations
  2. Dialog vs Bottom Sheet - Compare both UI patterns
  3. Integrated TextField - All-in-one solution with validation
  4. Custom Styled TextField - Manual integration with custom design
  5. Compact Layouts - Space-efficient variants
  6. Custom Strings & Localization - Override default texts
  7. Search & Validation Features - Advanced phone number search and validation

Features:

Notes

Known Limitations

Inspiration and Credit

Contribute

Please fork this repository and contribute back using pull requests.

Ways to contribute:

Before contributing:

  1. Check existing issues and pull requests
  2. Run the test suite: ./gradlew test
  3. Follow the existing code style
  4. Add tests for new features

If this project helps you, give it a ⭐ Star on GitHub!

License

Copyright 2024 ahmmedrejowan

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.