| كيفية تحسين تجربة المستخدم باستخدام التنقل في Kotlin Compose |
كيفية إدارة الحالة والتنقل في Kotlin Compose بسهولة
في Jetpack Compose، يمكن التعامل مع التنقل في الصفحة باستخدام NavHost وNavController دون الحاجة إلى استخدام مكتبات مثل Hilt أو ViewModel. يمكننا تحقيق ذلك ببساطة عن طريق معالجة المنطق في العناصر المركبة والتحكم في التنقل بين الشاشات باستخدام NavController.في هذا المقال سنرى كيفية إنشاء تطبيق بسيط يتكون من صفحتين، صفحة “رئيسية” وصفحة “خضراء”، وسنشرح كيفية التنقل بينهما دون استخدام ViewModel أو Hilt.
تثبيت المكتبات داخل libs.versions.toml
اضف المكتبات التاليه اذا لم تكن موجوده لديك في ملفات المشروع
[versions]
hiltCompiler = "2.51.1"
hiltNavigationCompose = "1.0.0"
kotlin = "1.9.0"
navigationCompose = "2.8.0"
[libraries]
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltCompiler" }
hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hiltCompiler" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
[plugins]
dagger-hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hiltCompiler" }
تهيئة ملف build.gradle.kts (app)
قم باضافات المكتبات اللازمه وايضا plugins في المكان المخصص لها كما هو موضح
plugins {
alias(libs.plugins.dagger.hilt.android)
kotlin("kapt")
}
...
dependencies {
implementation("androidx.constraintlayout:constraintlayout-compose:1.0.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.5")
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.hilt.android)
kapt(libs.hilt.compiler)
}
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
alias(libs.plugins.dagger.hilt.android) apply false
}كيفية التنقل بين الصفحات بدون hilt و viewmodel
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge() // تفعيل التصميم الحافة إلى الحافة
setContent {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") { CounterController(navController) }
composable("green") { SecondScreen(navController) }
}
}
}
}
@Composable
fun CounterController(state: ItemUiState = ItemUiState(), onClick: (ItemModel) -> Unit = {}, navController: NavHostController) {
Scaffold(
content = { paddingValues ->
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.background(Color.Blue)
.padding(paddingValues),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Button(onClick = {
navController.navigate("green")
}) {
Text("Click")
}
}
}
)
}
تحسين عمليات الانتقال بين الصفحات عن طريق sealed class
package com.example.compose
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
enum class Screen {
HOME,
Details,
}
sealed class NavigationItem(val route: String) {
object Home : NavigationItem(Screen.HOME.name)
object Details : NavigationItem(Screen.Details.name)
}
@Composable
fun AppNavHost(
navController: NavHostController,
startDestination: String = NavigationItem.Home.route,
) {
NavHost(
navController = navController, startDestination = startDestination
) {
composable(NavigationItem.Home.route) { CounterController(navController) }
composable(NavigationItem.Details.route) { SecondScreen(navController) }
}
}
// Main
setContent {
AppNavHost(
navController = rememberNavController(),
)
}
شرح تحسين عمليات الانتقال باستخدام sealed و enum
كيفية استخدام navigation مع hilt
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
AppNavHost(
navController = rememberNavController(),
)
}
}
}
@HiltAndroidApp
class EntryPage() : Application() {}
والانتقال الى ملف manifest واضافة الصفحة الرئيسيه التي ترث من application
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:name=".EntryPage"
...
/>
لماذا يجب عمل HiltAndroidApp و AndroidEntryPoint ؟
كيفة التنقل الى الصفحات مع نقل البيانات
data class ItemUiState(
val args :String = ""
)
// ViewModel
init {
val passedArgs: String? = savedStateHandle["name"]
if (passedArgs != null) {
updateText(passedArgs)
}
getData()
}
fun updateText(newText: String) {
_state.update {
it.copy(args = newText)
}
}
// Navigation
@Composable
fun AppNavHost(
navController: NavHostController,
startDestination: String = NavigationItem.Home.route,
) {
NavHost(
navController = navController, startDestination = startDestination
) {
composable(NavigationItem.Home.route,) { CounterController(navController) }
composable(
route = "${NavigationItem.Details.route}/{name}",
arguments = listOf(navArgument("name") { type = NavType.StringType }) )
{ SecondScreen(navController) }
}
}
// First Screen
@Composable
fun CounterController(navController: NavHostController) {
val viewModel: CounterViewModel = hiltViewModel()
val state by viewModel.state.collectAsState()
ShowItems(
state = state,
onClick = {name -> navController.navigate("${NavigationItem.Details.route}/$name") },
onChange = { name -> viewModel.updateText(name) })
}
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.background(Color.Blue)
.padding(paddingValues),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
TextField(
value = state.args,
onValueChange = onChange
)
Button(
onClick = {
onClick(state.args)
}
)
{
Text("Click") }
}
// Second Screen
@Composable
fun SecondScreen(nav: NavHostController) {
val viewModel: CounterViewModel = hiltViewModel()
val state by viewModel.state.collectAsState()
val context = LocalContext.current
Scaffold(
content = {
paddingValues ->
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.background(Color.Green)
.padding(paddingValues),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(state.args)
10.spacerHeight()
Button(onClick = {
Toast.makeText(context, state.args, Toast.LENGTH_SHORT).show()
})
{
Text("Click") }
}
}
)
}
شرح الكود نقل البيانات اثناء التنقل بين الصفحات في كومبوس
dependencies {
implementation(libs.kotlinx.serialization.json)
}
plugins {
...
alias(libs.plugins.kotlin.serialization)
}
#----- gradle/libs.versions.toml
[versions]
serialization = "1.6.3"
[libraries]
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization"
[plugins]
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } }@Composable
fun AppSerializableHost(navController: NavHostController,) {
NavHost(
navController = navController, startDestination = ScreenTest
) {
composable<ScreenHome>{ SecondScreen(nav = navController) }
composable<ScreenHomeTwo>{ it -> TutorialsScreen(it=it) }
composable<ScreenTest>{ DataStoreInput() }
}
}
@Serializable
object ScreenHome
@Serializable
object ScreenTest
@Serializable
data class ScreenHomeTwo(
val title: String,
val id: Int
)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TutorialsScreen(modifier: Modifier = Modifier,it : NavBackStackEntry) {
val args = it.toRoute<ScreenHomeTwo>()
val sheetState = rememberModalBottomSheetState()
val scope = rememberCoroutineScope()
var bottomSheetShow by remember {
mutableStateOf(false)
}
Scaffold { paddingValues ->
if (bottomSheetShow) {
ModalBottomSheet(
sheetState = sheetState,
onDismissRequest = { bottomSheetShow = false }
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(15.dp)
) {
Text("Hi I'm ${args.title} my id is ${args.id}")
Text("----------")
Text("You Can close Bottom From Here")
Spacer(modifier = Modifier.height(100.dp))
Button(onClick = {
scope.launch {
sheetState.hide()
}.invokeOnCompletion {
bottomSheetShow = false
}
}) {
Text("Close")
}
}
}
}
}
}



