معماری رابط کاربری را بنویسید

در Compose، رابط کاربری تغییرناپذیر است - هیچ راهی برای به‌روزرسانی آن پس از ترسیم وجود ندارد. چیزی که می‌توانید کنترل کنید، وضعیت رابط کاربری شماست. هر بار که وضعیت رابط کاربری تغییر می‌کند، Compose بخش‌هایی از درخت رابط کاربری را که تغییر کرده‌اند، دوباره ایجاد می‌کند . Composableها می‌توانند وضعیت را بپذیرند و رویدادها را نمایش دهند - برای مثال، یک TextField یک مقدار را می‌پذیرد و یک فراخوانی onValueChange را نمایش می‌دهد که از کنترل‌کننده فراخوانی درخواست می‌کند مقدار را تغییر دهد.

var name by remember { mutableStateOf("") }
OutlinedTextField(
    value = name,
    onValueChange = { name = it },
    label = { Text("Name") }
)

از آنجا که composableها حالت را می‌پذیرند و رویدادها را افشا می‌کنند، الگوی جریان داده یک‌طرفه به خوبی با Jetpack Compose سازگار است. این راهنما بر نحوه پیاده‌سازی الگوی جریان داده یک‌طرفه در Compose، نحوه پیاده‌سازی رویدادها و نگهدارنده‌های حالت و نحوه کار با ViewModelها در Compose تمرکز دارد.

جریان داده یک طرفه

جریان داده یک‌طرفه (UDF) یک الگوی طراحی است که در آن حالت به سمت پایین و رویدادها به سمت بالا جریان می‌یابند. با پیروی از جریان داده یک‌طرفه، می‌توانید کامپوننت‌هایی را که حالت را در رابط کاربری نمایش می‌دهند، از بخش‌هایی از برنامه خود که حالت را ذخیره و تغییر می‌دهند، جدا کنید.

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

  1. رویداد : بخشی از رابط کاربری یک رویداد تولید می‌کند و آن را به سمت بالا ارسال می‌کند، مانند کلیک یک دکمه که برای مدیریت به ViewModel ارسال می‌شود؛ یا رویدادی از لایه‌های دیگر برنامه شما ارسال می‌شود، مانند نشان دادن اینکه جلسه کاربر منقضی شده است.
  2. به‌روزرسانی وضعیت : یک کنترل‌کننده رویداد ممکن است وضعیت را تغییر دهد.
  3. نمایش وضعیت : دارنده وضعیت، وضعیت را به پایین منتقل می‌کند و رابط کاربری آن را نمایش می‌دهد.
رویدادها از رابط کاربری به سمت بالا و به سمت دارنده وضعیت (state holder) جریان می‌یابند، و وضعیت از دارنده وضعیت به سمت پایین و به سمت رابط کاربری (UI) جریان می‌یابد.
شکل ۱. جریان داده یک‌طرفه.

پیروی از این الگو هنگام استفاده از Jetpack Compose مزایای متعددی را ارائه می‌دهد:

  • قابلیت آزمایش : جدا کردن حالت از رابط کاربری که آن را نمایش می‌دهد، آزمایش هر دو را به صورت جداگانه آسان‌تر می‌کند.
  • کپسوله‌سازی حالت : از آنجا که حالت فقط می‌تواند در یک مکان به‌روزرسانی شود و فقط یک منبع حقیقت برای حالت یک ترکیب‌پذیر وجود دارد، احتمال کمتری وجود دارد که به دلیل حالت‌های متناقض، باگ ایجاد کنید.
  • ثبات رابط کاربری : تمام به‌روزرسانی‌های وضعیت بلافاصله با استفاده از نگهدارنده‌های وضعیت قابل مشاهده، مانند StateFlow یا LiveData ، در رابط کاربری منعکس می‌شوند.

جریان داده یک‌طرفه در Jetpack Compose

Composableها بر اساس وضعیت (state) و رویدادها کار می‌کنند. برای مثال، یک TextField فقط زمانی به‌روزرسانی می‌شود که پارامتر value آن به‌روزرسانی شود و یک فراخوانی onValueChange را نمایش دهد - رویدادی که درخواست می‌کند مقدار به مقدار جدیدی تغییر یابد. Compose شیء State را به عنوان یک نگهدارنده مقدار تعریف می‌کند و تغییرات در مقدار وضعیت، یک ترکیب مجدد را آغاز می‌کند. می‌توانید وضعیت را بسته به مدت زمانی که نیاز به یادآوری مقدار دارید، در یک remember { mutableStateOf(value) } یا یک rememberSaveable { mutableStateOf(value) نگه دارید.

نوع مقدار TextField composable، String است، بنابراین می‌تواند از هر جایی بیاید - از یک مقدار hardcode شده، از یک ViewModel، یا از composable والد ارسال شده. لازم نیست آن را در یک شیء State نگه دارید، اما باید هنگام فراخوانی onValueChange مقدار را به‌روزرسانی کنید.

پارامترهای قابل ترکیب را تعریف کنید

هنگام تعریف پارامترهای حالت یک composable، سوالات زیر را در نظر داشته باشید:

  • چقدر قابل استفاده مجدد یا انعطاف پذیر است؟
  • پارامترهای حالت چگونه بر عملکرد این ترکیب‌پذیر تأثیر می‌گذارند؟

برای افزایش جداسازی و استفاده مجدد، هر composable باید کمترین مقدار اطلاعات ممکن را در خود جای دهد. برای مثال، هنگام ساخت یک composable برای نگهداری هدر یک مقاله خبری، ترجیحاً فقط اطلاعاتی را که باید نمایش داده شوند، به جای کل مقاله خبری، ارسال کنید:

@Composable
fun Header(title: String, subtitle: String) {
    // Recomposes when title or subtitle have changed.
}

@Composable
fun Header(news: News) {
    // Recomposes when a new instance of News is passed in.
}

گاهی اوقات، استفاده از پارامترهای منفرد نیز عملکرد را بهبود می‌بخشد - برای مثال، اگر News حاوی اطلاعات بیشتری نسبت به title و subtitle باشد، هر زمان که نمونه جدیدی از News به Header(news) ارسال شود، composable دوباره ترکیب می‌شود، حتی اگر title و subtitle تغییر نکرده باشند.

تعداد پارامترهایی که ارسال می‌کنید را با دقت در نظر بگیرید. داشتن یک تابع با پارامترهای زیاد، ارگونومی تابع را کاهش می‌دهد، بنابراین در این حالت گروه‌بندی آنها در یک کلاس ترجیح داده می‌شود.

رویدادها در آهنگسازی

هر ورودی به برنامه شما باید به عنوان یک رویداد نمایش داده شود: ضربه‌ها، تغییرات متن و حتی تایمرها یا سایر به‌روزرسانی‌ها. از آنجایی که این رویدادها وضعیت رابط کاربری شما را تغییر می‌دهند، ViewModel باید آنها را مدیریت کرده و وضعیت رابط کاربری را به‌روزرسانی کند.

لایه رابط کاربری هرگز نباید خارج از یک کنترل‌کننده رویداد تغییر وضعیت دهد، زیرا این امر می‌تواند باعث ایجاد ناسازگاری‌ها و اشکالات در برنامه شما شود.

ترجیحاً مقادیر تغییرناپذیر را برای حالت و لامبداهای کنترل‌کننده رویداد ارسال کنید. این رویکرد مزایای زیر را دارد:

  • شما قابلیت استفاده مجدد را بهبود می‌بخشید.
  • شما تأیید می‌کنید که رابط کاربری شما مقدار state را مستقیماً تغییر نمی‌دهد.
  • شما از مشکلات همزمانی جلوگیری می‌کنید زیرا مطمئن می‌شوید که حالت از نخ دیگری تغییر نمی‌کند.
  • اغلب، شما پیچیدگی کد را کاهش می‌دهید.

برای مثال، یک composable که یک String و یک lambda را به عنوان پارامتر می‌پذیرد، می‌تواند از بسیاری از زمینه‌ها فراخوانی شود و قابلیت استفاده مجدد بالایی دارد. فرض کنید نوار برنامه بالایی در برنامه شما همیشه متن را نمایش می‌دهد و یک دکمه برگشت دارد. می‌توانید یک Composable عمومی‌تر MyAppTopAppBar تعریف کنید که متن و دسته دکمه برگشت را به عنوان پارامتر دریافت کند:

@Composable
fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) {
    TopAppBar(
        title = {
            Text(
                text = topAppBarText,
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .fillMaxSize()
                    .wrapContentSize(Alignment.Center)
            )
        },
        navigationIcon = {
            IconButton(onClick = onBackPressed) {
                Icon(
                    Icons.AutoMirrored.Filled.ArrowBack,
                    contentDescription = localizedString
                )
            }
        },
        // ...
    )
}

ViewModelها، حالت‌ها و رویدادها: یک مثال

با استفاده از ViewModel و mutableStateOf ، می‌توانید در صورت وجود یکی از شرایط زیر، جریان داده یک‌طرفه را نیز در برنامه خود ایجاد کنید:

  • وضعیت رابط کاربری شما با استفاده از نگهدارنده‌های وضعیت قابل مشاهده، مانند StateFlow یا LiveData ، نمایش داده می‌شود.
  • ViewModel رویدادهایی را که از رابط کاربری یا سایر لایه‌های برنامه شما می‌آیند، مدیریت می‌کند و نگهدارنده وضعیت (state holder) را بر اساس رویدادها به‌روزرسانی می‌کند.

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

صفحه نمایش چهار حالت دارد:

  • خروج از سیستم : زمانی که کاربر هنوز وارد سیستم نشده است.
  • در حال انجام : زمانی که برنامه شما سعی دارد با انجام یک فراخوانی شبکه، کاربر را وارد سیستم کند.
  • خطا : زمانی که هنگام ورود به سیستم خطایی رخ داده است.
  • ورود : زمانی که کاربر وارد سیستم شده است.

شما می‌توانید این حالت‌ها را به عنوان یک کلاس مهر و موم شده مدل‌سازی کنید. ViewModel حالت را به عنوان یک State نمایش می‌دهد، حالت اولیه را تنظیم می‌کند و در صورت نیاز حالت را به‌روزرسانی می‌کند. ViewModel همچنین رویداد ورود به سیستم را با نمایش یک متد onSignIn() مدیریت می‌کند.

class MyViewModel : ViewModel() {
    private val _uiState = mutableStateOf<UiState>(UiState.SignedOut)
    val uiState: State<UiState>
        get() = _uiState

    // ...
}

علاوه بر رابط برنامه‌نویسی mutableStateOf ، Compose افزونه‌هایی برای LiveData ، Flow و Observable ارائه می‌دهد تا به عنوان یک شنونده ثبت شوند و مقدار را به عنوان یک حالت نمایش دهند.

class MyViewModel : ViewModel() {
    private val _uiState = MutableLiveData<UiState>(UiState.SignedOut)
    val uiState: LiveData<UiState>
        get() = _uiState

    // ...
}

@Composable
fun MyComposable(viewModel: MyViewModel) {
    val uiState = viewModel.uiState.observeAsState()
    // ...
}

بیشتر بدانید

برای کسب اطلاعات بیشتر در مورد معماری در Jetpack Compose، به منابع زیر مراجعه کنید:

نمونه‌ها

{% کلمه به کلمه %} {% فعل کمکی %} {% کلمه به کلمه %} {% فعل کمکی %}