عرض النتائج

بدءًا من الإصدار 1.2.0 من Navigation 3، يمكنك عرض النتائج من الوجهات باستخدام واجهة برمجة التطبيقات ResultEventBus.

يوفّر ResultEventBus نموذجَي تواصل:

  • النتائج المستندة إلى الأحداث: للأحداث المؤقتة التي تحدث لمرة واحدة (مثل عرض شريط معلومات التأكيد أو تشغيل تأثير جانبي) باستخدام ResultEffect.
  • النتائج المستندة إلى الحالة: لمراقبة أحدث نتيجة في Compose State باستخدام conflateAsState.

إعداد ناقل أحداث النتائج

لإتاحة ResultEventBus لوجهاتك القابلة للإنشاء، أضِف rememberResultEventBusNavEntryDecorator إلى قائمة أدوات التزيين التي تم تمريرها إلى NavDisplay. يوفّر ذلك لكل محتوى وجهة مكوّن LocalResultEventBus محلي.

NavDisplay(
    /* ... */
    entryDecorators = listOf(
        rememberSaveableStateHolderNavEntryDecorator(),
        rememberResultEventBusNavEntryDecorator()
    )
)

مفاتيح النتائج

يحدّد ResultEventBus كل نتيجة ويوجهها باستخدام مفتاح. يطابق المرسلون والمستلمون النتائج باستخدام المفتاح نفسه.

يمكنك تحديد مفاتيح النتائج بطريقتَين:

  • المفاتيح الصريحة: يمكنك تحديد مفتاح صريح (مثل resultKey = "pickup_address"). استخدِم المفاتيح الصريحة عند عرض أنواع شائعة (مثل String أو Boolean أو العناصر الأساسية)، أو عندما تعرض وجهات متعدّدة نُسخًا مختلفة من نوع البيانات نفسه.
  • المفاتيح المستندة إلى النوع: عندما لا تحدّد مفتاحًا صريحًا، تنشئ ResultEventBus مفتاحًا تلقائيًا باستخدام تمثيل toString لنوع النتيجة KClass (مثل Contact::class.toString()). استخدِم المفاتيح المستندة إلى النوع لأنواع البيانات المميّزة الخاصة بالنطاق.

عرض النتائج من وجهة معيّنة

للحفاظ على إمكانية إعادة استخدام الدوال المركّبة للشاشة واختبارها، لا يمكنك الوصول إلى LocalResultEventBus مباشرةً داخل واجهة مستخدم الشاشة. بدلاً من ذلك، يمكنك عرض دوال lambda لعمليات الاستدعاء من شاشتك. في entryProvider، يمكنك معالجة طلب معاودة الاتصال من خلال إرسال النتيجة باستخدام LocalResultEventBus.current والرجوع إلى الصفحة السابقة.

يمكنك إرسال النتائج باستخدام مفتاح نتيجة واضح:

import androidx.compose.runtime.Composable
import androidx.navigation3.runtime.result.LocalResultEventBus

entry<AddressPickerRoute> {
    val resultBus = LocalResultEventBus.current

    AddressPickerScreen(
        onAddressSelected = { selectedAddress: Address ->
            resultBus.sendResult(
                resultKey = "pickup_address",
                result = selectedAddress
            )
            navigator.goBack()
        }
    )
}

يمكنك أيضًا إرسال النتائج باستخدام مفتاح مشتق من النوع:

import androidx.compose.runtime.Composable
import androidx.navigation3.runtime.result.LocalResultEventBus

entry<ContactPickerRoute> {
    val resultBus = LocalResultEventBus.current

    ContactPickerScreen(
        onContactSelected = { selectedContact: Contact ->
            resultBus.sendResult(result = selectedContact)
            navigator.goBack()
        }
    )
}

تلقّي النتائج

يمكن للوجهات استخدام النتائج باستخدام التأثيرات المستندة إلى الأحداث أو الملاحظات المستندة إلى الحالة.

واجهة برمجة التطبيقات السلوك حالات الاستخدام المقترَحة
ResultEffect في قائمة الانتظار: تتم معالجة جميع النتائج الصادرة للمفتاح بالترتيب. الأحداث والآثار الجانبية التي تحدث لمرة واحدة (مثل عرض شريط معلومات أو إعادة التوجيه إلى ViewModel)
conflateAsState Conflated: يتم تجاهل النتائج المتوسطة والاحتفاظ بأحدث نتيجة فقط كـ Compose State. معدِّلات حالة واجهة المستخدم البسيطة (مثل علامات الفلاتر النشطة أو عمليات إلغاء الاختيار)

التعامل مع الأحداث التي تقع لمرة واحدة باستخدام ResultEffect

استخدِم ResultEffect عند التعامل مع الأحداث التي تحدث لمرة واحدة، مثل تفعيل الإحصاءات أو عرض شريط معلومات أو إعادة توجيه نتيجة إلى ViewModel.

يحتفظ "ResultEffect" بقائمة انتظار للنتائج الواردة. إذا تم إرسال نتائج متعددة لمفتاح معيّن، يعالج ResultEffect كل نتيجة بالترتيب الذي تم إرسالها به. بالإضافة إلى ذلك، يتم تشغيل ResultEffect في نطاق الكوروتين، ما يتيح لك استدعاء دوال التعليق مباشرةً داخل نص التأثير.

يمكنك الاستماع إلى النتائج المرتبطة بمفتاح نتيجة صريح:

import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation3.runtime.result.ResultEffect

@Composable
fun RideSummaryScreen(
    onOpenAddressPicker: (key: String) -> Unit,
    viewModel: RideSummaryViewModel = viewModel()
) {
    ResultEffect<Address>(resultKey = "pickup_address") { address ->
        viewModel.onPickupAddressSelected(address)
    }

    ResultEffect<Address>(resultKey = "destination_address") { address ->
        viewModel.onDestinationAddressSelected(address)
    }

    RideSummaryContent(
        pickupAddress = viewModel.pickupAddress,
        destinationAddress = viewModel.destinationAddress,
        onPickPickup = { onOpenAddressPicker("pickup_address") },
        onPickDestination = { onOpenAddressPicker("destination_address") }
    )
}

يمكنك أيضًا الاستماع إلى النتائج باستخدام مفتاح مشتق من النوع:

import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation3.runtime.result.ResultEffect

@Composable
fun ComposeMessageScreen(
    onPickContact: () -> Unit,
    snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
    viewModel: ComposeMessageViewModel = viewModel()
) {
    ResultEffect<Contact> { contact ->
        // Suspending calls are supported directly in the effect body
        snackbarHostState.showSnackbar("Selected ${contact.name}")
        viewModel.onRecipientSelected(contact)
    }

    ComposeMessageContent(
        recipient = viewModel.recipient,
        onPickContact = onPickContact
    )
}

عند التنقّل بين الوجهات، يتم تنفيذ ResultEffect من خلال تسلسل مراحل النشاط التالي:

  1. المرسِل يُرسل: تُرسل وجهة المرسِل نتيجة باستخدام resultBus.sendResult(resultKey = "pickup_address", address) وتُزيل الأنشطة السابقة.
  2. يدخل المستلِم إلى التركيبة: تصبح وجهة المستلِم هي الشاشة النشطة، ويبدأ ResultEffect في الاستماع إلى النتائج.
  3. معالجة النتائج من قِبل المستلِم: يتلقّى ResultEffect النتائج وينفّذ محتوى التأثير لكل نتيجة يتم إرسالها لهذا المفتاح، ويعالج جميع الانبعاثات بالترتيب الذي تم إرسالها به.
  4. مغادرة الوجهة المستقبلة لـ Composition: عند إزالة الوجهة المستقبلة من الأنشطة السابقة، يغادر ResultEffect Composition ويتوقف عن الاستماع إلى النتائج.

مراقبة أحدث النتائج حسب الولاية مع conflateAsState

عندما تحتاج فقط إلى قيمة النتيجة الأخيرة لتعديل حالة واجهة المستخدم المحلية أو فلترتها مباشرةً وتريد أن يعيد Compose إنشاء الواجهة تلقائيًا عند تعديل النتيجة، يمكنك استدعاء conflateAsState في ResultEventBus.

يمكنك الاطّلاع على النتائج المرتبطة بمفتاح نتيجة واضح:

import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.navigation3.runtime.result.LocalResultEventBus

@Composable
fun ThemePreviewScreen(
    onOpenColorPicker: (key: String) -> Unit
) {
    val resultBus = LocalResultEventBus.current

    val primaryColor by resultBus.conflateAsState<Color>(
        resultKey = "primary_color",
        defaultValue = MaterialTheme.colorScheme.primary
    )

    val accentColor by resultBus.conflateAsState<Color>(
        resultKey = "accent_color",
        defaultValue = MaterialTheme.colorScheme.tertiary
    )

    ThemePreviewContent(
        primaryColor = primaryColor,
        accentColor = accentColor,
        onPickPrimary = { onOpenColorPicker("primary_color") },
        onPickAccent = { onOpenColorPicker("accent_color") }
    )
}

يمكنك أيضًا الاطّلاع على النتائج باستخدام مفتاح مشتق من النوع:

import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.navigation3.runtime.result.LocalResultEventBus

@Composable
fun FilterableProductListScreen(
    initialFilter: ProductFilter = ProductFilter.All,
    onOpenFilterPicker: () -> Unit
) {
    val resultBus = LocalResultEventBus.current

    // Observe latest filter result as Compose State, starting with initialFilter
    val activeFilter by resultBus.conflateAsState<ProductFilter>(
        defaultValue = initialFilter
    )

    ProductListContent(
        activeFilter = activeFilter,
        onOpenFilterPicker = onOpenFilterPicker
    )
}

Hoist ResultEventBus

ينشئ rememberResultEventBusNavEntryDecorator تلقائيًا ResultEventBus الخاص به ويتذكّره داخليًا باستخدام rememberResultEventBus.

يمكنك إنشاء ResultEventBus ورفعه بشكل صريح عندما تحتاج إلى:

  • يمكنك تمرير مثيل ResultEventBus مباشرةً إلى المكوّنات غير القابلة للتركيب أو الرسوم البيانية لحقن التبعية.
  • يمكنك إرسال النتائج أو الاطّلاع عليها من خلال هيكل التطبيق ذي المستوى الأعلى (مثل شريط التطبيق أو لائحة التنقل) خارج التسلسل الهرمي للوجهة.

لرفع ResultEventBus، أنشِئه باستخدام rememberResultEventBus ومرِّره إلى rememberResultEventBusNavEntryDecorator(resultEventBus):

import androidx.compose.runtime.Composable
import androidx.navigation3.runtime.result.rememberResultEventBus
import androidx.navigation3.runtime.result.rememberResultEventBusNavEntryDecorator
import androidx.navigation3.ui.NavDisplay

// Hoist the ResultEventBus at the top level
val resultEventBus = rememberResultEventBus()

// Pass the hoisted bus to the decorator
val resultEventBusNavEntryDecorator =
    rememberResultEventBusNavEntryDecorator<NavKey>(
        resultEventBus = resultEventBus
    )

NavDisplay(
    /* ... */
    entryDecorators = listOf(
        rememberSaveableStateHolderNavEntryDecorator(),
        resultEventBusNavEntryDecorator
    )
)

إدارة النتائج ومحوها

عندما تستهلك وجهة نتيجة لمرة واحدة، يمكنك محوها من ناقل الأحداث باستخدام removeResult. يمنع ذلك الناقل من إعادة تسليم الأحداث السابقة إلى المراقبين الجدد عند إعادة دخول الوجهات إلى التركيب:

import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation3.runtime.result.LocalResultEventBus
import androidx.navigation3.runtime.result.ResultEffect

@Composable
fun NotificationSettingsScreen(
    viewModel: NotificationViewModel = viewModel()
) {
    val resultBus = LocalResultEventBus.current

    ResultEffect<ConfirmationResult>(resultKey = "confirm_permission") { confirmation ->
        viewModel.onPermissionConfirmed(confirmation)

        // Clear the result after consumption to prevent re-delivery
        resultBus.removeResult(resultKey = "confirm_permission")
    }
}

يمكنك محو النتائج حسب المفتاح الصريح (resultBus.removeResult(resultKey)) أو حسب المفتاح المشتق من النوع (resultBus.removeResult<T>()). للحصول على تفاصيل حول مطابقة المفاتيح، يُرجى الاطّلاع على مفاتيح النتائج.

وصفات طعام

For complete runnable code examples demonstrating different result-passing strategies, see the following recipes: