
[!NOTE] Full tutorial on this library on YouTube. You can check it out here
Version 0.2 brings major UI/UX improvements along with critical bug fixes and enhanced stability:
Migration from 0.1.x: No breaking changes! Just update your dependencies. See the Migration Guide below.
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:
| Different Use Cases | Country Picker Dialog | Picker Bottom Sheet |
|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
You can download the test apk to try out all features - Download APK
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven {
url = uri("https://jitpack.io")
}
}
}
Add this to your moduleβs build.gradle.kts file (latest version ):
dependencies {
implementation("com.github.ahmmedrejowan:CountryCodePickerCompose:0.2")
}
There are 4 different ways to use this library:
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:
selectedCountry: Country - Currently selected countryonCountrySelected: (Country) -> Unit - Callback when country is selectedshowSheet: Boolean - true for bottom sheet, false for dialog (default: false)viewCustomization: ViewCustomization - Customize the picker button appearancepickerCustomization: PickerCustomization - Customize the picker dialog/sheetAn 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:
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
)
)
}
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:
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
)
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
)
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:
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:
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:
Good news! Version 0.2 has NO breaking changes. Your existing code will continue to work without modifications.
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+
}
}
β 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
π 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)
./gradlew clean buildIf you encounter any issues during migration:
The included sample app demonstrates 7 comprehensive usage patterns with 15+ examples:
Features:
Please fork this repository and contribute back using pull requests.
Ways to contribute:
Before contributing:
./gradlew testIf this project helps you, give it a β Star on GitHub!
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.