ملاحظات دیگر

در حالی که مهاجرت از Views به Compose صرفاً مربوط به UI است، برای انجام یک انتقال ایمن و تدریجی باید موارد زیادی را در نظر گرفت. این صفحه شامل برخی ملاحظات هنگام انتقال برنامه مبتنی بر View شما به Compose است.

در حال انتقال تم برنامه شما

Material Design سیستم طراحی پیشنهادی برای قالب بندی برنامه های اندروید است.

برای برنامه‌های مبتنی بر View، سه نسخه از Material موجود است:

  • Material Design 1 با استفاده از کتابخانه AppCompat (یعنی Theme.AppCompat.* )
  • Material Design 2 با استفاده از کتابخانه MDC-Android (یعنی Theme.MaterialComponents.* )
  • Material Design 3 با استفاده از کتابخانه MDC-Android (یعنی Theme.Material3.* )

برای برنامه های Compose، دو نسخه از Material موجود است:

  • Material Design 2 با استفاده از کتابخانه Compose Material (یعنی androidx.compose.material.MaterialTheme )
  • Material Design 3 با استفاده از کتابخانه Compose Material 3 (یعنی androidx.compose.material3.MaterialTheme )

اگر سیستم طراحی برنامه شما در موقعیت مناسبی است، توصیه می کنیم از آخرین نسخه (Material 3) استفاده کنید. راهنماهای انتقال هم برای Views و هم برای Compose وجود دارد:

هنگام ایجاد صفحه‌های جدید در Compose، صرف نظر از اینکه از کدام نسخه طراحی متریال استفاده می‌کنید، اطمینان حاصل کنید که یک MaterialTheme قبل از هر ترکیبی که UI از کتابخانه‌های Compose Material منتشر می‌کند، اعمال کنید. اجزای Material ( Button ، Text ، و غیره) به یک MaterialTheme در محل بستگی دارند و رفتار آنها بدون آن تعریف نشده است.

همه نمونه‌های Jetpack Compose از یک تم Compose سفارشی استفاده می‌کنند که بر روی MaterialTheme ساخته شده است.

برای کسب اطلاعات بیشتر، طراحی سیستم‌ها را در Compose and Migring themes XML to Compose ببینید.

اگر از مؤلفه ناوبری در برنامه خود استفاده می کنید، برای اطلاعات بیشتر به «پیمایش با نوشتن - قابلیت همکاری و انتقال ناوبری Jetpack به نگارش ناوبری» مراجعه کنید.

رابط کاربری ترکیبی Compose/Views خود را آزمایش کنید

پس از انتقال بخش‌هایی از برنامه‌تان به Compose، آزمایش برای اطمینان از اینکه چیزی را خراب نکرده‌اید بسیار مهم است.

وقتی یک فعالیت یا قطعه از Compose استفاده می کند، باید به جای استفاده از ActivityScenarioRule از createAndroidComposeRule استفاده کنید. createAndroidComposeRule ActivityScenarioRule با ComposeTestRule ادغام می کند که به شما امکان می دهد کد Compose و View را همزمان تست کنید.

class MyActivityTest {
    @Rule
    @JvmField
    val composeTestRule = createAndroidComposeRule<MyActivity>()

    @Test
    fun testGreeting() {
        val greeting = InstrumentationRegistry.getInstrumentation()
            .targetContext.resources.getString(R.string.greeting)

        composeTestRule.onNodeWithText(greeting).assertIsDisplayed()
    }
}

برای کسب اطلاعات بیشتر در مورد آزمایش، به آزمایش طرح‌بندی نوشتن خود مراجعه کنید. برای همکاری با چارچوب‌های آزمایش UI، قابلیت همکاری با Espresso و قابلیت همکاری با UiAutomator را ببینید.

ادغام Compose با معماری برنامه موجود شما

الگوهای معماری جریان داده های یک جهته (UDF) با Compose یکپارچه کار می کنند. اگر برنامه به جای آن از انواع دیگری از الگوهای معماری استفاده می‌کند، مانند Model View Presenter (MVP)، توصیه می‌کنیم آن قسمت از UI را قبل یا در حین پذیرش Compose به UDF منتقل کنید.

استفاده از ViewModel در Compose

اگر از کتابخانه ViewModel کامپوننت‌های معماری استفاده می‌کنید، می‌توانید با فراخوانی تابع viewModel() به ViewModel از هر composable دسترسی داشته باشید، همانطور که در Compose و کتابخانه‌های دیگر توضیح داده شده است.

هنگام استفاده از Compose، مراقب استفاده از نوع ViewModel یکسان در Composable های مختلف باشید زیرا عناصر ViewModel از محدوده View-lifecycle پیروی می کنند. اگر از کتابخانه ناوبری استفاده شود، دامنه یا فعالیت میزبان، قطعه، یا نمودار ناوبری خواهد بود.

به عنوان مثال، اگر composable ها در یک اکتیویتی میزبانی شوند، viewModel() همیشه همان نمونه ای را برمی گرداند که تنها پس از پایان فعالیت پاک می شود. در مثال زیر، همان کاربر ("user1") دو بار مورد استقبال قرار می گیرد زیرا همان نمونه GreetingViewModel در همه composable ها تحت فعالیت میزبان مجددا استفاده می شود. اولین نمونه ViewModel ایجاد شده در دیگر composable ها مجددا استفاده می شود.

class GreetingActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            MaterialTheme {
                Column {
                    GreetingScreen("user1")
                    GreetingScreen("user2")
                }
            }
        }
    }
}

@Composable
fun GreetingScreen(
    userId: String,
    viewModel: GreetingViewModel = viewModel(  
        factory = GreetingViewModelFactory(userId)  
    )
) {
    val messageUser by viewModel.message.observeAsState("")
    Text(messageUser)
}

class GreetingViewModel(private val userId: String) : ViewModel() {
    private val _message = MutableLiveData("Hi $userId")
    val message: LiveData<String> = _message
}

class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return GreetingViewModel(userId) as T
    }
}

از آنجایی که نمودارهای ناوبری شامل عناصر ViewModel نیز می‌شوند، کامپوزیشن‌هایی که مقصدی در یک نمودار ناوبری هستند، نمونه‌های متفاوتی از ViewModel دارند. در این حالت، ViewModel به چرخه حیات مقصد اختصاص داده می‌شود و زمانی که مقصد از پشت پشتی حذف می‌شود، پاک می‌شود. در مثال زیر، زمانی که کاربر به صفحه پروفایل می‌رود، نمونه جدیدی از GreetingViewModel ایجاد می‌شود.

@Composable
fun MyApp() {
    NavHost(rememberNavController(), startDestination = "profile/{userId}") {
        /* ... */
        composable("profile/{userId}") { backStackEntry ->
            GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "")
        }
    }
}

منبع حقیقت

وقتی Compose را در بخشی از UI استفاده می کنید، ممکن است Compose و کد سیستم View نیاز به اشتراک گذاری داده ها داشته باشند. در صورت امکان، توصیه می کنیم حالت مشترک را در کلاس دیگری که از بهترین شیوه های UDF استفاده شده توسط هر دو پلتفرم پیروی می کند، کپسوله کنید. به عنوان مثال، در یک ViewModel که جریانی از داده های مشترک را برای انتشار به روز رسانی داده ها در معرض دید قرار می دهد.

با این حال، اگر داده‌هایی که قرار است به اشتراک گذاشته شوند قابل تغییر باشند یا به شدت به یک عنصر UI متصل شده باشند، همیشه ممکن نیست. در این صورت، یک سیستم باید منبع حقیقت باشد و آن سیستم باید هرگونه به روز رسانی داده را با سیستم دیگر به اشتراک بگذارد. به عنوان یک قاعده کلی، منبع حقیقت باید متعلق به هر عنصری باشد که به ریشه سلسله مراتب UI نزدیک‌تر است.

تالیف به عنوان منبع حقیقت

از SideEffect composable برای انتشار حالت Compose به کد غیر Compose استفاده کنید. در این مورد، منبع حقیقت در یک composable نگهداری می شود که به روز رسانی های حالت را ارسال می کند.

به عنوان مثال، کتابخانه تجزیه و تحلیل شما ممکن است به شما اجازه دهد با پیوست کردن ابرداده های سفارشی ( ویژگی های کاربر در این مثال) به همه رویدادهای تجزیه و تحلیل بعدی، جمعیت کاربر خود را بخش بندی کنید. برای ارتباط نوع کاربری کاربر فعلی با کتابخانه تجزیه و تحلیل خود، از SideEffect برای به روز رسانی مقدار آن استفاده کنید.

@Composable
fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics {
    val analytics: FirebaseAnalytics = remember {
        FirebaseAnalytics()
    }

    // On every successful composition, update FirebaseAnalytics with
    // the userType from the current User, ensuring that future analytics
    // events have this metadata attached
    SideEffect {
        analytics.setUserProperty("userType", user.userType)
    }
    return analytics
}

برای اطلاعات بیشتر، به عوارض جانبی در نوشتن مراجعه کنید.

به سیستم به عنوان منبع حقیقت نگاه کنید

اگر سیستم View مالک حالت است و آن را با Compose به اشتراک می‌گذارد، توصیه می‌کنیم که حالت را در اشیاء mutableStateOf بپیچید تا آن را به صورت رشته‌ای برای Compose ایمن کنید. اگر از این روش استفاده می کنید، توابع ترکیب پذیر ساده می شوند زیرا دیگر منبع حقیقت را ندارند، اما سیستم View باید حالت تغییرپذیر و View هایی را که از آن حالت استفاده می کنند به روز کند.

در مثال زیر، یک CustomViewGroup شامل یک TextView و یک ComposeView با یک TextField قابل ترکیب در داخل است. TextView باید محتوای آنچه کاربر در TextField تایپ می کند را نشان دهد.

class CustomViewGroup @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : LinearLayout(context, attrs, defStyle) {

    // Source of truth in the View system as mutableStateOf
    // to make it thread-safe for Compose
    private var text by mutableStateOf("")

    private val textView: TextView

    init {
        orientation = VERTICAL

        textView = TextView(context)
        val composeView = ComposeView(context).apply {
            setContent {
                MaterialTheme {
                    TextField(value = text, onValueChange = { updateState(it) })
                }
            }
        }

        addView(textView)
        addView(composeView)
    }

    // Update both the source of truth and the TextView
    private fun updateState(newValue: String) {
        text = newValue
        textView.text = newValue
    }
}

در حال انتقال رابط کاربری مشترک

اگر به تدریج به Compose مهاجرت می کنید، ممکن است لازم باشد از عناصر رابط کاربری مشترک در سیستم Compose و View استفاده کنید. برای مثال، اگر برنامه شما یک جزء CallToActionButton سفارشی دارد، ممکن است لازم باشد از آن در هر دو صفحه Compose و View-based استفاده کنید.

در Compose، عناصر UI به اشتراک‌گذاشته‌شده به قابلیت‌هایی تبدیل می‌شوند که می‌توانند مجدداً در سراسر برنامه استفاده شوند، صرف نظر از اینکه عنصر با استفاده از XML یا نمای سفارشی استایل‌بندی شده باشد. برای مثال، می‌توانید یک CallToActionButton ایجاد کنید که می‌تواند برای مؤلفه Button تماس به اقدام سفارشی خود ایجاد کند.

برای استفاده از composable در صفحه نمایش مبتنی بر View، یک نمای بسته بندی سفارشی ایجاد کنید که از AbstractComposeView گسترش می یابد. همانطور که در مثال زیر نشان داده شده است، در Content composable لغو شده آن، قابل ترکیبی را که ایجاد کردید در قالب Compose خود قرار دهید:

@Composable
fun CallToActionButton(
    text: String,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            containerColor = MaterialTheme.colorScheme.secondary
        ),
        onClick = onClick,
        modifier = modifier,
    ) {
        Text(text)
    }
}

class CallToActionViewButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : AbstractComposeView(context, attrs, defStyle) {

    var text by mutableStateOf("")
    var onClick by mutableStateOf({})

    @Composable
    override fun Content() {
        YourAppTheme {
            CallToActionButton(text, onClick)
        }
    }
}

توجه داشته باشید که پارامترهای composable تبدیل به متغیرهای قابل تغییر در نمای سفارشی می شوند. این باعث می شود که نمای CallToActionViewButton سفارشی مانند یک نمای سنتی قابل تورم و قابل استفاده باشد. نمونه ای از این را با View Binding در زیر ببینید:

class ViewBindingActivity : ComponentActivity() {

    private lateinit var binding: ActivityExampleBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityExampleBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.callToAction.apply {
            text = getString(R.string.greeting)
            onClick = { /* Do something */ }
        }
    }
}

اگر مؤلفه سفارشی حاوی حالت تغییرپذیر است، به State source of true مراجعه کنید.

اولویت بندی حالت تفکیک از ارائه

به طور سنتی، یک View حالتی است. یک View فیلدهایی را مدیریت می کند که علاوه بر نحوه نمایش آن، چه چیزی را نشان می دهد. هنگامی که یک View به Compose تبدیل می‌کنید، به دنبال جداسازی داده‌های ارائه‌شده برای دستیابی به یک جریان داده یک طرفه باشید، همانطور که در حالت hoisting توضیح داده شده است.

به عنوان مثال، یک View دارای ویژگی visibility است که نشان می دهد قابل مشاهده، نامرئی یا از بین رفته است. این ویژگی ذاتی View است. در حالی که سایر قطعات کد ممکن است نمایان بودن یک View را تغییر دهند، فقط خود View واقعاً می داند که نمای فعلی آن چقدر است. منطق حصول اطمینان از قابل مشاهده بودن یک View می تواند مستعد خطا باشد و اغلب به خود View گره خورده است.

در مقابل، Compose با استفاده از منطق شرطی در Kotlin، نمایش اجزای کاملاً متفاوت را آسان می‌کند:

@Composable
fun MyComposable(showCautionIcon: Boolean) {
    if (showCautionIcon) {
        CautionIcon(/* ... */)
    }
}

با طراحی، CautionIcon نیازی به دانستن یا اهمیتی ندارد که چرا نمایش داده می شود، و هیچ مفهومی از visibility وجود ندارد: یا در ترکیب قرار دارد یا نیست.

با جداسازی دقیق مدیریت حالت و منطق ارائه، می‌توانید آزادانه‌تر نحوه نمایش محتوا را به عنوان تبدیل حالت به UI تغییر دهید. توانایی بالا بردن حالت در صورت نیاز نیز باعث می شود که مواد ترکیبی قابل استفاده مجدد باشند، زیرا مالکیت دولتی انعطاف پذیرتر است.

اجزای محصور شده و قابل استفاده مجدد را تبلیغ کنید

عناصر View اغلب تصوری از محل زندگی خود دارند: داخل یک Activity ، یک Dialog ، یک Fragment یا جایی در داخل سلسله مراتب View دیگر. از آنجایی که آنها اغلب از فایل‌های طرح‌بندی استاتیک پر می‌شوند، ساختار کلی یک View بسیار سفت و سخت است. این منجر به اتصال محکم‌تر می‌شود و تغییر یا استفاده مجدد یک View را دشوارتر می‌کند.

به عنوان مثال، یک View سفارشی ممکن است فرض کند که یک نمای فرزند از نوع خاصی با یک شناسه خاص دارد و خصوصیات آن را مستقیماً در پاسخ به برخی اقدامات تغییر دهد. این عناصر View کاملاً با هم مرتبط می‌کند: اگر View سفارشی نتواند فرزند را پیدا کند، ممکن است خراب شود یا خراب شود، و احتمالاً کودک نمی‌تواند بدون والد View سفارشی دوباره استفاده شود.

این مشکل در Compose با قابلیت های قابل استفاده مجدد کمتر است. والدین می‌توانند به راحتی وضعیت و تماس‌ها را مشخص کنند، بنابراین می‌توانید مواد قابل استفاده مجدد را بدون نیاز به دانستن مکان دقیق استفاده از آنها بنویسید.

@Composable
fun AScreen() {
    var isEnabled by rememberSaveable { mutableStateOf(false) }

    Column {
        ImageWithEnabledOverlay(isEnabled)
        ControlPanelWithToggle(
            isEnabled = isEnabled,
            onEnabledChanged = { isEnabled = it }
        )
    }
}

در مثال بالا، هر سه قسمت بیشتر کپسوله شده و کمتر جفت شده اند:

  • ImageWithEnabledOverlay فقط باید بداند که وضعیت فعلی isEnabled چیست. نیازی به دانستن وجود ControlPanelWithToggle یا حتی نحوه کنترل آن نیست.

  • ControlPanelWithToggle نمی داند که ImageWithEnabledOverlay وجود دارد. ممکن است صفر، یک یا چند راه وجود داشته باشد که isEnabled نمایش داده شود، و ControlPanelWithToggle نیازی به تغییر ندارد.

  • برای والدین، اهمیتی ندارد که ImageWithEnabledOverlay یا ControlPanelWithToggle تا چه حد عمیق تو در تو باشد. این کودکان می توانند تغییرات را متحرک کنند، محتوا را مبادله کنند یا محتوا را به کودکان دیگر منتقل کنند.

این الگو به عنوان وارونگی کنترل شناخته می شود که می توانید اطلاعات بیشتری در مورد آن در مستندات CompositionLocal بخوانید.

مدیریت تغییر اندازه صفحه نمایش

داشتن منابع مختلف برای اندازه‌های مختلف پنجره یکی از راه‌های اصلی ایجاد طرح‌بندی View Responsive است. در حالی که منابع واجد شرایط هنوز گزینه ای برای تصمیم گیری در مورد طرح بندی در سطح صفحه نمایش هستند، Compose تغییر طرح بندی ها را به طور کامل در کد با منطق شرطی معمولی بسیار آسان تر می کند. برای کسب اطلاعات بیشتر به کلاس های اندازه پنجره مراجعه کنید.

علاوه بر این، برای آشنایی با تکنیک‌هایی که Compose برای ایجاد رابط‌های کاربری تطبیقی ​​ارائه می‌دهد، به پشتیبانی از اندازه‌های مختلف صفحه مراجعه کنید.

پیمایش تو در تو با Views

برای اطلاعات بیشتر در مورد نحوه فعال کردن interop پیمایش تودرتو بین عناصر View قابل پیمایش و composableهای قابل پیمایش، تودرتو در هر دو جهت، از طریق Nested scrolling interop را بخوانید.

نوشتن در RecyclerView

Composable ها در RecyclerView از RecyclerView نسخه 1.3.0-alpha02 کارایی دارند. مطمئن شوید که حداقل از نسخه 1.3.0-alpha02 RecyclerView استفاده می کنید تا این مزایا را ببینید.

WindowInsets با Views تداخل دارد

هنگامی که صفحه نمایش شما دارای هر دو کد Views و Compose در یک سلسله مراتب است، ممکن است لازم باشد که ورودی های پیش فرض را لغو کنید. در این مورد، شما باید به صراحت بگویید که در کدام یک از اینست ها باید مصرف کرد و کدام یک باید آنها را نادیده گرفت.

به عنوان مثال، اگر بیرونی‌ترین طرح‌بندی شما یک طرح‌بندی Android View است، باید ورودی‌های موجود در سیستم View را مصرف کنید و آنها را برای Compose نادیده بگیرید. از طرف دیگر، اگر بیرونی‌ترین چیدمان شما قابل ترکیب است، باید ورودی‌ها را در Compose مصرف کنید و بر اساس آن، Composable‌های AndroidView را پاک کنید.

به طور پیش‌فرض، هر ComposeView تمام ورودی‌ها را در سطح مصرف WindowInsetsCompat مصرف می‌کند. برای تغییر این رفتار پیش‌فرض، ComposeView.consumeWindowInsets را روی false تنظیم کنید.

برای اطلاعات بیشتر، مستندات WindowInsets در Compose را بخوانید.

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}