با Firebase و Jetpack Compose یک برنامه اندروید بسازید

۱. مقدمه

آخرین به‌روزرسانی: 2022-11-16

ساخت اپلیکیشن اندروید با Firebase و Jetpack Compose

در این آزمایشگاه کد، شما یک برنامه اندروید به نام Make It So خواهید ساخت. رابط کاربری این برنامه کاملاً با Jetpack Compose ساخته شده است، که ابزار مدرن اندروید برای ساخت رابط کاربری بومی است - این ابزار شهودی است و به کد کمتری نسبت به نوشتن فایل‌های .xml و اتصال آنها به Activityها، Fragments یا Viewها نیاز دارد.

اولین قدم برای درک چگونگی عملکرد خوب Firebase و Jetpack Compose با هم، درک معماری مدرن اندروید است. یک معماری خوب، درک سیستم، توسعه و نگهداری آن را آسان می‌کند، زیرا نحوه سازماندهی و ارتباط اجزا با یکدیگر را به وضوح نشان می‌دهد. در دنیای اندروید، معماری پیشنهادی Model - View - ViewModel نام دارد. Model نشان دهنده لایه‌ای است که به داده‌ها در برنامه دسترسی پیدا می‌کند. View لایه UI است و نباید چیزی در مورد منطق کسب و کار بداند. و ViewModel جایی است که منطق کسب و کار اعمال می‌شود، که گاهی اوقات نیاز است ViewModel لایه Model را فراخوانی کند.

ما اکیداً توصیه می‌کنیم این مقاله را بخوانید تا بفهمید چگونه Model - View - ViewModel در یک برنامه اندروید ساخته شده با Jetpack Compose اعمال می‌شود، زیرا این کار درک کدبیس و تکمیل مراحل بعدی را آسان‌تر می‌کند.

آنچه خواهید ساخت

Make It So یک برنامه ساده برای فهرست کارها است که به کاربر اجازه می‌دهد وظایف را اضافه و ویرایش کند، پرچم، اولویت و تاریخ سررسید اضافه کند و وظایف را به عنوان تکمیل شده علامت‌گذاری کند. تصاویر زیر دو صفحه اصلی این برنامه را نشان می‌دهند: صفحه ایجاد وظیفه و صفحه اصلی با لیست وظایف ایجاد شده.

آن را طوری تنظیم کنید که صفحه وظیفه را اضافه کندصفحه اصلی Make it So

شما برخی از ویژگی‌هایی را که در این برنامه وجود ندارند، اضافه خواهید کرد:

  • احراز هویت کاربران با ایمیل و رمز عبور
  • یک شنونده به مجموعه Firestore اضافه کنید و کاری کنید که رابط کاربری به تغییرات واکنش نشان دهد
  • اضافه کردن ردپاهای سفارشی برای نظارت بر عملکرد کد خاص در برنامه
  • با استفاده از Remote Config یک گزینه‌ی تغییر ویژگی ایجاد کنید و از اجرای مرحله‌ای برای راه‌اندازی آن استفاده کنید.

آنچه یاد خواهید گرفت

  • نحوه استفاده از احراز هویت فایربیس، نظارت بر عملکرد، پیکربندی از راه دور و Cloud Firestore در یک برنامه مدرن اندروید
  • چگونه API های Firebase را در معماری MVVM قرار دهیم
  • نحوه انعکاس تغییرات ایجاد شده با API های Firebase در رابط کاربری Compose

آنچه نیاز دارید

۲. برنامه نمونه را دریافت کنید و Firebase را راه‌اندازی کنید

کد برنامه نمونه را دریافت کنید

مخزن گیت‌هاب را از خط فرمان کلون کنید:

git clone https://github.com/FirebaseExtended/make-it-so-android.git

ایجاد یک پروژه فایربیس

  1. با استفاده از حساب گوگل خود وارد کنسول فایربیس شوید.
  2. برای ایجاد یک پروژه جدید، روی دکمه کلیک کنید و سپس نام پروژه را وارد کنید (برای مثال، Compose Firebase codelab ).
  3. روی ادامه کلیک کنید.
  4. در صورت درخواست، شرایط Firebase را مرور و قبول کنید و سپس روی ادامه کلیک کنید.
  5. (اختیاری) دستیار هوش مصنوعی را در کنسول Firebase (با نام "Gemini در Firebase") فعال کنید.
  6. برای این codelab، برای استفاده از گزینه‌های پیشرفته هدف‌گیری با Remote Config به Google Analytics نیاز دارید، بنابراین گزینه Google Analytics را فعال نگه دارید. برای تنظیم Google Analytics، دستورالعمل‌های روی صفحه را دنبال کنید.
  7. روی ایجاد پروژه کلیک کنید، منتظر بمانید تا پروژه شما آماده شود و سپس روی ادامه کلیک کنید.

یک برنامه اندروید به پروژه Firebase خود اضافه کنید

در پروژه Firebase خود، می‌توانید برنامه‌های مختلفی را ثبت کنید: برای اندروید، iOS، وب، Flutter و Unity.

همانطور که در اینجا می‌بینید، گزینه اندروید را انتخاب کنید:

بررسی اجمالی پروژه فایربیس

سپس این مراحل را دنبال کنید:

  1. com.example.makeitso به عنوان نام بسته وارد کنید و به صورت اختیاری، یک نام مستعار وارد کنید. برای این codelab، نیازی به اضافه کردن گواهی امضای اشکال‌زدایی ندارید.
  2. برای ثبت برنامه خود و دسترسی به فایل پیکربندی Firebase، روی Next کلیک کنید.
  3. برای دانلود فایل پیکربندی خود و ذخیره آن در دایرکتوری make-it-so-android/app روی «دانلود google-services.json» کلیک کنید.
  4. روی Next کلیک کنید. از آنجا که SDK های Firebase از قبل در فایل build.gradle در پروژه نمونه گنجانده شده‌اند، برای رفتن به مراحل بعدی، روی Next کلیک کنید.
  5. برای پایان ، روی ادامه برای کنسول کلیک کنید.

برای اینکه برنامه Make it So به درستی کار کند، قبل از رفتن به سراغ کد، دو کار وجود دارد که باید در کنسول انجام دهید: ارائه دهندگان احراز هویت را فعال کنید و پایگاه داده Firestore را ایجاد کنید.

تنظیم احراز هویت

ابتدا، بیایید احراز هویت را فعال کنیم تا کاربران بتوانند وارد برنامه شوند:

  1. از منوی Build ، گزینه Authentication (احراز هویت) را انتخاب کنید و سپس روی Get Started (شروع به کار) کلیک کنید.
  2. از کارت روش ورود ، ایمیل/رمز عبور را انتخاب کنید و آن را فعال کنید.
  3. سپس، روی افزودن ارائه‌دهنده جدید کلیک کنید و گزینه «ناشناس» را انتخاب و فعال کنید.

راه اندازی کلود فایر استور

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

  1. در پنل سمت چپ کنسول Firebase، گزینه Build را باز کرده و سپس Firestore database را انتخاب کنید.
  2. روی ایجاد پایگاه داده کلیک کنید.
  3. شناسه پایگاه داده را روی (default) تنظیم کنید.
  4. مکانی را برای پایگاه داده خود انتخاب کنید، سپس روی Next کلیک کنید.
    برای یک اپلیکیشن واقعی، شما می‌خواهید مکانی را انتخاب کنید که به کاربرانتان نزدیک باشد.
  5. روی شروع در حالت آزمایشی کلیک کنید. سلب مسئولیت مربوط به قوانین امنیتی را مطالعه کنید.
    در مراحل بعدی این بخش، قوانین امنیتی را برای ایمن‌سازی داده‌های خود اضافه خواهید کرد. بدون اضافه کردن قوانین امنیتی برای پایگاه داده خود، برنامه را به صورت عمومی توزیع یا افشا نکنید .
  6. روی ایجاد کلیک کنید.

بیایید لحظه‌ای وقت بگذاریم و قوانین امنیتی قوی برای پایگاه داده Firestore ایجاد کنیم.

  1. داشبورد Firestore را باز کنید و به برگه قوانین بروید.
  2. قوانین امنیتی را به صورت زیر به‌روزرسانی کنید:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /tasks/{document} {
      allow create: if request.auth != null;
      allow read, update, delete: if request.auth != null
        && resource.data.userId == request.auth.uid
        && request.data.userId == resource.data.userId;
    }
  }
}

این قوانین اساساً می‌گویند که هر کاربر وارد شده به برنامه می‌تواند برای خود در هر مجموعه‌ای سندی ایجاد کند. سپس، پس از ایجاد، فقط کاربری که آن سند را ایجاد کرده است قادر به مشاهده، به‌روزرسانی یا حذف آن سند خواهد بود.

برنامه را اجرا کنید

حالا آماده‌ی اجرای برنامه هستید! پوشه‌ی make-it-so-android/start را در اندروید استودیو باز کنید و برنامه را اجرا کنید (این کار را می‌توان با استفاده از یک شبیه‌ساز اندروید یا یک دستگاه اندروید واقعی انجام داد).

۳. احراز هویت فایربیس

کدام ویژگی را قرار است اضافه کنید؟

در وضعیت فعلی برنامه نمونه Make It So ، کاربر می‌تواند بدون نیاز به ورود به سیستم، استفاده از برنامه را شروع کند. برای دستیابی به این هدف، از احراز هویت ناشناس استفاده می‌کند. با این حال، حساب‌های ناشناس به کاربر اجازه دسترسی به داده‌های خود در دستگاه‌های دیگر یا حتی در جلسات آینده را نمی‌دهند. اگرچه احراز هویت ناشناس برای یک ورود گرم مفید است، اما همیشه باید این گزینه را برای کاربران فراهم کنید که به شکل دیگری از ورود به سیستم روی آورند. با توجه به این نکته، در این آزمایشگاه کد، احراز هویت ایمیل و رمز عبور را به برنامه Make It So اضافه خواهید کرد.

وقت کدنویسی است!

به محض اینکه کاربر با تایپ ایمیل و رمز عبور، یک حساب کاربری ایجاد کرد، باید از API احراز هویت فایربیس، یک اعتبارنامه ایمیل درخواست کنید، سپس اعتبارنامه جدید را به حساب ناشناس پیوند دهید. فایل AccountServiceImpl.kt را در اندروید استودیو باز کنید و تابع linkAccount را طوری به‌روزرسانی کنید که به شکل زیر باشد:

مدل/سرویس/impl/AccountServiceImpl.kt

override suspend fun linkAccount(email: String, password: String) {
    val credential = EmailAuthProvider.getCredential(email, password)
    auth.currentUser!!.linkWithCredential(credential).await()
}

حالا SignUpViewModel.kt را باز کنید و تابع سرویس linkAccount را درون بلوک launchCatching از تابع onSignUpClick فراخوانی کنید:

screens/sign_up/SignUpViewModel.kt

launchCatching {
    accountService.linkAccount(email, password)
    openAndPopUp(SETTINGS_SCREEN, SIGN_UP_SCREEN)
}

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

به محض اینکه SettingsScreen دوباره باز شد، باید مطمئن شوید که گزینه‌های Sign in و Create account حذف شده‌اند، زیرا اکنون کاربر از قبل احراز هویت شده است. برای انجام این کار، بیایید کاری کنیم که SettingsViewModel به وضعیت کاربر فعلی (که در AccountService.kt موجود است) گوش دهد تا بررسی کند که آیا حساب ناشناس است یا خیر. برای انجام این کار، uiState در SettingsViewModel.kt به‌روزرسانی کنید تا به شکل زیر باشد:

صفحات/تنظیمات/تنظیماتViewModel.kt

val uiState = accountService.currentUser.map {
    SettingsUiState(it.isAnonymous)
}

آخرین کاری که باید انجام دهید، به‌روزرسانی uiState در SettingsScreen.kt برای جمع‌آوری حالت‌های منتشر شده توسط SettingsViewModel است:

صفحه نمایش/تنظیمات/تنظیماتScreen.kt

val uiState by viewModel.uiState.collectAsState(
    initial = SettingsUiState(false)
)

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

وقت آزمایش است!

برنامه Make it So را اجرا کنید و با کلیک روی نماد چرخ‌دنده در گوشه سمت راست بالای صفحه، به تنظیمات بروید. از آنجا، روی گزینه ایجاد حساب کاربری کلیک کنید:

صفحه تنظیمات Make it Soصفحه ثبت نام را بسازید

برای ایجاد حساب کاربری خود، یک ایمیل معتبر و یک رمز عبور قوی وارد کنید. حساب کاربری باید کار کند و شما به صفحه تنظیمات هدایت خواهید شد، جایی که دو گزینه جدید مشاهده خواهید کرد: خروج و حذف حساب کاربری. می‌توانید با کلیک روی تب کاربران، حساب کاربری جدید ایجاد شده را در داشبورد احراز هویت در کنسول Firebase بررسی کنید.

۴. کلود فایراستور

کدام ویژگی را قرار است اضافه کنید؟

برای Cloud Firestore، یک شنونده به مجموعه Firestore اضافه خواهید کرد که اسنادی را که نشان دهنده وظایف نمایش داده شده در Make it So هستند، ذخیره می‌کند. پس از افزودن این شنونده، هر به‌روزرسانی انجام شده در این مجموعه را دریافت خواهید کرد.

وقت کدنویسی است!

Flow موجود در StorageServiceImpl.kt را به صورت زیر به‌روزرسانی کنید:

مدل/سرویس/impl/StorageServiceImpl.kt

override val tasks: Flow<List<Task>>
    get() =
      auth.currentUser.flatMapLatest { user ->
        firestore.collection(TASK_COLLECTION).whereEqualTo(USER_ID_FIELD, user.id).dataObjects()
      }

این کد بر اساس user.id یک شنونده به مجموعه وظایف اضافه می‌کند. هر وظیفه توسط یک سند در مجموعه‌ای به نام tasks نمایش داده می‌شود و هر یک از آنها فیلدی به نام userId دارند. لطفاً توجه داشته باشید که اگر وضعیت currentUser تغییر کند (مثلاً با خروج از سیستم)، یک Flow جدید منتشر می‌شود.

حالا باید کاری کنید که Flow در TasksViewModel.kt مشابه جریان در سرویس باشد:

صفحات/وظایف/TasksViewModel.kt

val tasks = storageService.tasks

و آخرین نکته این است که composable function در TasksScreens.kt که نمایانگر رابط کاربری است، از این جریان آگاه باشد و آن را به عنوان یک state جمع‌آوری کند. هر بار که state تغییر می‌کند، تابع composable به طور خودکار خود را دوباره ترکیب می‌کند و جدیدترین state را به کاربر نمایش می‌دهد. این کد را به TasksScreen composable function اضافه کنید:

صفحات/وظایف/TasksScreen.kt

val tasks = viewModel
    .tasks
    .collectAsStateWithLifecycle(emptyList())

زمانی که تابع composable به این حالت‌ها دسترسی پیدا کرد، می‌توانید LazyColumn (که ساختاری است که برای نمایش یک لیست روی صفحه استفاده می‌کنید) را به صورت زیر به‌روزرسانی کنید:

صفحات/وظایف/TasksScreen.kt

LazyColumn {
    items(tasks.value, key = { it.id }) { taskItem ->
        TaskItem( [...] )
    }
}

وقت آزمایش است!

برای آزمایش کارکرد آن، با استفاده از برنامه (با کلیک روی دکمه افزودن در گوشه پایین سمت راست صفحه) یک وظیفه جدید اضافه کنید. پس از اتمام ایجاد وظیفه، باید در مجموعه Firestore در کنسول Firestore ظاهر شود. اگر در دستگاه‌های دیگر با همان حساب کاربری وارد Make it So شوید، می‌توانید موارد انجام کار خود را ویرایش کرده و به‌روزرسانی آنها را در تمام دستگاه‌ها به صورت آنی مشاهده کنید.

۵. نظارت بر عملکرد

کدام ویژگی را قرار است اضافه کنید؟

عملکرد نکته بسیار مهمی است که باید به آن توجه شود زیرا اگر عملکرد برنامه شما خوب نباشد و کاربران برای انجام یک کار ساده با آن زمان زیادی صرف کنند، به احتمال زیاد از استفاده از آن منصرف می‌شوند. به همین دلیل است که گاهی اوقات جمع‌آوری برخی معیارها در مورد یک مسیر خاص که کاربر در برنامه شما طی می‌کند، مفید است. و برای کمک به شما در این زمینه، Firebase Performance Monitoring ردیابی‌های سفارشی را ارائه می‌دهد. مراحل بعدی را برای اضافه کردن ردیابی‌های سفارشی و اندازه‌گیری عملکرد در بخش‌های مختلف کد در Make it So دنبال کنید.

وقت کدنویسی است!

اگر فایل Performance.kt را باز کنید، یک تابع درون‌خطی به نام trace خواهید دید. این تابع، API نظارت بر عملکرد را برای ایجاد یک trace سفارشی فراخوانی می‌کند و نام trace را به عنوان پارامتر به آن ارسال می‌کند. پارامتر دیگری که می‌بینید، بلوک کدی است که می‌خواهید مانیتور کنید. معیار پیش‌فرض جمع‌آوری‌شده برای هر trace، زمان لازم برای اجرای کامل آن است:

مدل/خدمات/عملکرد.kt

inline fun <T> trace(name: String, block: Trace.() -> T): T = Trace.create(name).trace(block)

شما می‌توانید انتخاب کنید که کدام بخش‌های کدبیس برای اندازه‌گیری مهم هستند و سپس ردیابی‌های سفارشی را به آن اضافه کنید. در اینجا مثالی از اضافه کردن یک ردیابی سفارشی به تابع linkAccount که قبلاً (در AccountServiceImpl.kt ) در این آزمایشگاه کد مشاهده کردید، آورده شده است:

مدل/سرویس/impl/AccountServiceImpl.kt

override suspend fun linkAccount(email: String, password: String): Unit =
  trace(LINK_ACCOUNT_TRACE) {
      val credential = EmailAuthProvider.getCredential(email, password)
      auth.currentUser!!.linkWithCredential(credential).await()
  }

حالا نوبت شماست! چند رد سفارشی به برنامه Make it So اضافه کنید و برای آزمایش اینکه آیا طبق انتظار کار می‌کند یا خیر، به بخش بعدی بروید.

وقت آزمایش است!

پس از اتمام افزودن ردپاهای سفارشی، برنامه را اجرا کنید و مطمئن شوید که از ویژگی‌هایی که می‌خواهید اندازه‌گیری کنید، چند بار استفاده می‌کنید. سپس به کنسول Firebase بروید و به داشبورد Performance بروید. در پایین صفحه، سه تب پیدا خواهید کرد: درخواست‌های شبکه ، ردپاهای سفارشی و رندر صفحه .

به برگهٔ «ردیاب‌های سفارشی» (Custom traces) بروید و بررسی کنید که ردپاهایی که در کدبیس اضافه کرده‌اید در آنجا نمایش داده می‌شوند و می‌توانید ببینید که معمولاً اجرای این قطعات کد چقدر طول می‌کشد.

۶. پیکربندی از راه دور

کدام ویژگی را قرار است اضافه کنید؟

موارد استفاده‌ی زیادی برای Remote Config وجود دارد، از تغییر ظاهر برنامه از راه دور گرفته تا پیکربندی رفتارهای مختلف برای بخش‌های مختلف کاربر. در این آزمایشگاه کد، شما از Remote Config برای ایجاد یک گزینه‌ی تغییر ویژگی استفاده خواهید کرد که ویژگی جدید ویرایش وظیفه را در برنامه‌ی Make it So نمایش یا پنهان می‌کند.

وقت کدنویسی است!

اولین کاری که باید انجام دهید ایجاد پیکربندی در کنسول Firebase است. برای انجام این کار، باید به داشبورد Remote Config بروید و روی دکمه Add parameter کلیک کنید. فیلدها را مطابق تصویر زیر پر کنید:

پیکربندی از راه دور، ایجاد یک پنجره پارامتر

پس از پر کردن همه فیلدها، می‌توانید روی دکمه ذخیره و سپس انتشار کلیک کنید. اکنون که پارامتر ایجاد شده و در پایگاه کد شما در دسترس است، باید کدی را که مقادیر جدید را به برنامه شما واکشی می‌کند، اضافه کنید. فایل ConfigurationServiceImpl.kt را باز کنید و پیاده‌سازی این دو تابع را به‌روزرسانی کنید:

مدل/سرویس/impl/ConfigurationServiceImpl.kt

override suspend fun fetchConfiguration(): Boolean {
  return remoteConfig.fetchAndActivate().await()
}

override val isShowTaskEditButtonConfig: Boolean
  get() = remoteConfig[SHOW_TASK_EDIT_BUTTON_KEY].asBoolean()

تابع اول مقادیر را از سرور دریافت می‌کند و به محض شروع برنامه، در SplashViewModel.kt فراخوانی می‌شود. این بهترین راه برای اطمینان از این است که به‌روزترین مقادیر از همان ابتدا در تمام صفحات در دسترس خواهند بود. اگر رابط کاربری یا رفتار برنامه را بعداً، زمانی که کاربر در حال انجام کاری است، تغییر دهید، تجربه کاربری خوبی نخواهد بود!

تابع دوم مقدار بولی را که برای پارامتری که اخیراً در کنسول ایجاد کرده‌اید منتشر شده است، برمی‌گرداند. و شما باید این اطلاعات را در TasksViewModel.kt با اضافه کردن موارد زیر به تابع loadTaskOptions بازیابی کنید:

صفحات/وظایف/TasksViewModel.kt

fun loadTaskOptions() {
  val hasEditOption = configurationService.isShowTaskEditButtonConfig
  options.value = TaskActionOption.getOptions(hasEditOption)
}

شما در حال بازیابی مقدار در خط اول هستید و از آن برای بارگذاری گزینه‌های منو برای آیتم‌های وظیفه در خط دوم استفاده می‌کنید. اگر مقدار false باشد، به این معنی است که منو شامل گزینه ویرایش نخواهد بود. اکنون که لیست گزینه‌ها را دارید، باید کاری کنید که رابط کاربری آن را به درستی نمایش دهد. از آنجایی که در حال ساخت برنامه‌ای با Jetpack Compose هستید، باید به دنبال composable function باشید که نحوه نمایش رابط کاربری TasksScreen را اعلام کند. بنابراین فایل TasksScreen.kt را باز کنید و LazyColum را به‌روزرسانی کنید تا به گزینه‌های موجود در TasksViewModel.kt اشاره کند:

صفحات/وظایف/TasksScreen.kt

val options by viewModel.options

LazyColumn {
  items(tasks.value, key = { it.id }) { taskItem ->
    TaskItem(
      options = options,
      [...]
    )
  }
}

TaskItem یک composable function دیگر است که نحوه نمایش رابط کاربری یک وظیفه واحد را اعلام می‌کند. و هر وظیفه دارای منویی با گزینه‌هایی است که با کلیک کاربر روی آیکون سه نقطه در انتهای آن نمایش داده می‌شود.

وقت آزمایش است!

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

  • اگر مقدار آن false باشد، هنگام کلیک روی آیکون سه نقطه، فقط باید دو گزینه ببینید؛
  • اگر true باشد، هنگام کلیک روی نماد سه نقطه، باید سه گزینه ببینید؛

چند بار مقدار را در کنسول تغییر دهید و برنامه را مجدداً راه اندازی کنید. به همین راحتی می‌توانید با استفاده از Remote Config ویژگی‌های جدید را در برنامه خود راه‌اندازی کنید!

۷. تبریک

تبریک می‌گویم، شما با موفقیت یک برنامه اندروید با Firebase و Jetpack Compose ساختید!

شما احراز هویت فایربیس، نظارت بر عملکرد، پیکربندی از راه دور و فروشگاه ابری فایراستور را به یک برنامه اندروید که کاملاً با Jetpack Compose برای رابط کاربری ساخته شده است، اضافه کردید و آن را با معماری MVVM پیشنهادی سازگار ساختید!

مطالعه بیشتر

اسناد مرجع