توصیه هایی برای معماری اندروید

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

بهترین شیوه‌های زیر بر اساس موضوع گروه‌بندی شده‌اند. هر کدام اولویتی دارند که نشان دهنده میزان قوت توصیه است. فهرست اولویت‌ها به شرح زیر است:

  • اکیداً توصیه می‌شود: این روش را اجرا کنید، مگر اینکه اساساً با رویکرد شما مغایرت داشته باشد.
  • توصیه می‌شود: این روش احتمالاً برنامه شما را بهبود می‌بخشد.
  • اختیاری: این روش می‌تواند در شرایط خاص، برنامه شما را بهبود بخشد.

معماری لایه‌ای

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

توصیه توضیحات
از یک لایه داده کاملاً تعریف‌شده استفاده کنید. لایه داده ، داده‌های برنامه را در معرض بقیه برنامه قرار می‌دهد و بخش عمده‌ای از منطق تجاری برنامه شما را در بر می‌گیرد.
  • حتی اگر فقط شامل یک منبع داده باشند، مخازن ایجاد کنید.
  • در برنامه‌های کوچک، می‌توانید انواع لایه‌های داده را در یک بسته یا ماژول data قرار دهید.
از یک لایه رابط کاربری (UI) کاملاً تعریف‌شده استفاده کنید. لایه رابط کاربری، داده‌های برنامه را روی صفحه نمایش می‌دهد و به عنوان نقطه اصلی تعامل کاربر عمل می‌کند. Jetpack Compose ابزار مدرن پیشنهادی برای ساخت رابط کاربری برنامه شماست.
  • در برنامه‌های کوچک، می‌توانید انواع لایه‌های داده را در یک بسته یا ماژول ui قرار دهید.
برای اطلاعات بیشتر در مورد بهترین شیوه‌های لایه رابط کاربری، به لایه رابط کاربری مراجعه کنید.
داده‌های برنامه را از لایه داده با استفاده از یک مخزن (repository) در معرض نمایش قرار دهید.

مطمئن شوید که کامپوننت‌های موجود در لایه رابط کاربری مانند composableها یا ViewModelها مستقیماً با منبع داده تعامل ندارند. نمونه‌هایی از منابع داده عبارتند از:

  • پایگاه‌های داده، DataStore، SharedPreferences، APIهای Firebase.
  • ارائه دهندگان موقعیت مکانی GPS
  • ارائه دهندگان داده بلوتوث
  • ارائه دهندگان وضعیت اتصال شبکه.
از کوروتین‌ها و جریان‌ها استفاده کنید. از کوروتین‌ها و جریان‌ها برای ارتباط بین لایه‌ها استفاده کنید.

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

از یک لایه دامنه استفاده کنید. اگر نیاز به استفاده مجدد از منطق کسب‌وکاری دارید که با لایه داده در چندین ViewModel تعامل دارد، یا می‌خواهید پیچیدگی منطق کسب‌وکار یک ViewModel خاص را ساده کنید، از یک لایه دامنه با موارد استفاده استفاده کنید.

لایه رابط کاربری

نقش لایه رابط کاربری نمایش داده‌های برنامه روی صفحه نمایش و ایفای نقش به عنوان نقطه اصلی تعامل کاربر است. در اینجا چند نمونه از بهترین شیوه‌ها برای لایه رابط کاربری آورده شده است:

توصیه توضیحات
جریان داده یک‌طرفه (UDF) را دنبال کنید. از اصول جریان داده یک‌طرفه (UDF) پیروی کنید، که در آن ViewModelها با استفاده از الگوی ناظر، وضعیت رابط کاربری را نمایش می‌دهند و از طریق فراخوانی متدها، اقدامات را از رابط کاربری دریافت می‌کنند.
اگر مزایای AAC ViewModels در برنامه شما صدق می‌کند، از آنها استفاده کنید. از AAC ViewModels برای مدیریت منطق کسب و کار و واکشی داده‌های برنامه برای نمایش وضعیت رابط کاربری به رابط کاربری استفاده کنید.

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

برای اطلاعات بیشتر در مورد مزایای ViewModelها، به ViewModel به عنوان یک نگهدارنده وضعیت منطق کسب و کار مراجعه کنید.

از مجموعه وضعیت رابط کاربری آگاه از چرخه حیات استفاده کنید. با استفاده از سازنده‌ی کوروتینِ آگاه از چرخه‌ی حیاتِ مناسب، collectAsStateWithLifecycle ، وضعیت رابط کاربری را از رابط کاربری جمع‌آوری کنید.

درباره collectAsStateWithLifecycle بیشتر بخوانید.

رویدادها را از ViewModel به UI ارسال نکنید. رویداد را فوراً در ViewModel پردازش کنید و باعث به‌روزرسانی وضعیت با نتیجه‌ی مدیریت رویداد شوید. برای اطلاعات بیشتر در مورد رویدادهای رابط کاربری، به Handle ViewModel events مراجعه کنید.
از یک برنامه تک فعالیتی استفاده کنید. اگر برنامه شما بیش از یک صفحه دارد، از Navigation 3 برای پیمایش بین صفحات و پیوند عمیق به برنامه خود استفاده کنید.
از جت‌پک کامپوز استفاده کنید. از Jetpack Compose برای ساخت برنامه‌های جدید برای تلفن‌ها، تبلت‌ها، دستگاه‌های تاشو و Wear OS استفاده کنید.

قطعه کد زیر نحوه جمع‌آوری وضعیت رابط کاربری را به شیوه‌ای آگاه از چرخه حیات (lifecycle-aware) شرح می‌دهد:

  @Composable
  fun MyScreen(
      viewModel: MyViewModel = viewModel()
  ) {
      val uiState by viewModel.uiState.collectAsStateWithLifecycle()
  }

ویو مدل

ViewModelها مسئول ارائه وضعیت رابط کاربری و دسترسی به لایه داده هستند. در اینجا چند نمونه از بهترین شیوه‌ها برای ViewModelها آورده شده است:

توصیه توضیحات
ViewModelها را مستقل از چرخه حیات اندروید نگه دارید. در ViewModelها، به هیچ نوع مرتبط با چرخه حیات ارجاع ندهید. Activity ، Context یا Resources به عنوان وابستگی ارسال نکنید. اگر چیزی در ViewModel به Context نیاز دارد، با دقت ارزیابی کنید که آیا در لایه مناسب قرار دارد یا خیر.
از کوروتین‌ها و جریان‌ها استفاده کنید.

ViewModel با استفاده از موارد زیر با لایه‌های داده یا دامنه تعامل می‌کند:

  • جریان‌های کاتلین برای دریافت داده‌های برنامه
  • توابع را برای انجام اقدامات با استفاده از viewModelScope suspend
از ViewModelها در سطح صفحه نمایش استفاده کنید.

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

  • ترکیبات سطح صفحه نمایش،
  • فعالیت‌ها/قطعات در نماها،
  • مقصدها یا نمودارها هنگام استفاده از ناوبری Jetpack .
از کلاس‌های نگهدارنده‌ی حالت ساده در کامپوننت‌های رابط کاربری قابل استفاده مجدد استفاده کنید. از کلاس‌های نگهدارنده‌ی حالت ساده برای مدیریت پیچیدگی در کامپوننت‌های رابط کاربری قابل استفاده مجدد استفاده کنید. وقتی این کار را انجام می‌دهید، حالت می‌تواند به صورت خارجی منتقل و کنترل شود.
AndroidViewModel استفاده نکنید. از کلاس ViewModel استفاده کنید، نه AndroidViewModel . از کلاس Application در ViewModel استفاده نکنید. در عوض، وابستگی را به رابط کاربری یا لایه داده منتقل کنید.
نمایش وضعیت رابط کاربری (UI). کاری کنید که ViewModel های شما از طریق یک ویژگی واحد به نام uiState ، داده‌ها را در اختیار رابط کاربری قرار دهند. اگر رابط کاربری چندین قطعه داده نامرتبط را نشان دهد، ماشین مجازی می‌تواند چندین ویژگی وضعیت رابط کاربری را در معرض نمایش قرار دهد .
  • uiState به یک StateFlow تبدیل کنید.
  • اگر داده‌ها به صورت جریانی از داده‌ها از لایه‌های دیگر سلسله مراتب می‌آیند، uiState با استفاده از عملگر stateIn و با استفاده از سیاست WhileSubscribed(5000) ایجاد کنید. (به این نمونه کد مراجعه کنید.)
  • برای موارد ساده‌تر که هیچ جریان داده‌ای از لایه داده نمی‌آید، استفاده از یک MutableStateFlow که به عنوان یک StateFlow تغییرناپذیر در معرض نمایش قرار گرفته است، قابل قبول است.
  • شما می‌توانید ${Screen}UiState را به عنوان یک کلاس داده انتخاب کنید که می‌تواند شامل داده‌ها، خطاها و سیگنال‌های بارگذاری باشد. این کلاس همچنین می‌تواند یک کلاس مهر و موم شده باشد اگر حالت‌های مختلف انحصاری باشند.

قطعه کد زیر نحوه نمایش وضعیت رابط کاربری از یک ViewModel را شرح می‌دهد:

@HiltViewModel
class BookmarksViewModel @Inject constructor(
    newsRepository: NewsRepository
) : ViewModel() {

    val feedState: StateFlow<NewsFeedUiState> =
        newsRepository
            .getNewsResourcesStream()
            .mapToFeedState(savedNewsResourcesState)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = NewsFeedUiState.Loading
            )

    // ...
}

چرخه حیات

برای کار با چرخه حیات اکتیویتی ، بهترین شیوه‌ها را دنبال کنید:

توصیه توضیحات
به جای لغو فراخوانی‌های چرخه حیات Activity ، از افکت‌های آگاه از چرخه حیات در composableها استفاده کنید.

برای اجرای وظایف مرتبط با رابط کاربری، متدهای چرخه حیات Activity ، مانند onResume ، را override نکنید. در عوض، از LifecycleEffects مربوط به Compose یا scopeهای coroutine آگاه از چرخه حیات استفاده کنید:

  • از LifecycleStartEffect برای انجام کار همزمان هنگام شروع و توقف فعالیت خود استفاده کنید.
  • از LifecycleResumeEffect برای انجام کار همزمان هنگام از سرگیری و مکث فعالیت خود استفاده کنید.
  • repeatOnLifecycle برای انجام کار ناهمزمان در پاسخ به رویدادهای چرخه عمر استفاده کنید.
  • با استفاده از collectAsStateWithLifecycle داده‌های ناهمزمان را از Flowها جمع‌آوری کنید.

قطعه کد زیر نحوه انجام عملیات با توجه به یک وضعیت چرخه حیات خاص را شرح می‌دهد:

  @Composable
  fun LocationChangedEffect(
    locationManager: LocationManager,
    onLocationChanged: (Location) -> Unit
  ) {
    val currentOnLocationChanged by rememberUpdatedState(onLocationChanged)

    LifecycleStartEffect(locationManager) {
        val listener = LocationListener { newLocation ->
            currentOnLocationChanged(newLocation)
        }

        try {
            locationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER,
                1000L,
                1f,
                listener,
            )
        } catch (e: SecurityException) {
            // TODO: Handle missing permissions
        }

        onStopOrDispose {
            locationManager.removeUpdates(listener)
        }
    }
  }

مدیریت وابستگی‌ها

هنگام مدیریت وابستگی‌ها بین اجزا، از بهترین شیوه‌ها پیروی کنید:

توصیه توضیحات
از تزریق وابستگی استفاده کنید. از بهترین شیوه‌های تزریق وابستگی ، عمدتاً در صورت امکان از تزریق سازنده، استفاده کنید.
در صورت لزوم، به یک جزء دسترسی داشته باشید. محدوده به یک ظرف وابستگی زمانی که نوع شامل داده‌های قابل تغییر است که باید به اشتراک گذاشته شوند یا نوع برای مقداردهی اولیه پرهزینه است و به طور گسترده در برنامه استفاده می‌شود.
از هیلت استفاده کنید. در برنامه‌های ساده از Hilt یا تزریق وابستگی دستی استفاده کنید. اگر پروژه شما به اندازه کافی پیچیده است - مثلاً اگر شامل هر یک از موارد زیر است - از Hilt استفاده کنید:
  • چندین صفحه نمایش با ViewModels
  • از WorkManager استفاده می‌کند
  • آیا ViewModelها به پشته ناوبری محدود شده‌اند؟

آزمایش

در ادامه به برخی از بهترین شیوه‌های آزمایش اشاره شده است:

توصیه توضیحات
بدانید چه چیزی را آزمایش کنید .

مگر اینکه پروژه به سادگی یک برنامه "hello world" باشد، آن را آزمایش کنید. حداقل موارد زیر را در آن بگنجانید:

  • تست‌های واحد برای ViewModelها، شامل Flowها
  • تست‌های واحد برای موجودیت‌های لایه داده - یعنی مخازن و منابع داده
  • تست‌های ناوبری رابط کاربری که به عنوان تست‌های رگرسیون در CI مفید هستند
چیزهای قلابی را به چیزهای ساختگی ترجیح می‌دهند. برای اطلاعات بیشتر در مورد استفاده از مقادیر جعلی، به بخش «استفاده از مقادیر آزمایشی مضاعف در اندروید» مراجعه کنید.
جریان‌های حالت (StateFlows) را آزمایش کنید. هنگام آزمایش StateFlow ، موارد زیر را انجام دهید:

برای اطلاعات بیشتر، به «چه چیزهایی را در اندروید آزمایش کنیم» و «طرح‌بندی Compose خود را آزمایش کنید» مراجعه کنید.

مدل‌ها

هنگام توسعه مدل‌ها در برنامه‌های خود، این بهترین شیوه‌ها را رعایت کنید:

توصیه توضیحات
در برنامه‌های پیچیده، برای هر لایه یک مدل ایجاد کنید.

در برنامه‌های پیچیده، در صورت لزوم، مدل‌های جدید را در لایه‌ها یا کامپوننت‌های مختلف ایجاد کنید. مثال‌های زیر را در نظر بگیرید:

  • یک منبع داده از راه دور می‌تواند مدلی را که از طریق شبکه دریافت می‌کند، به یک کلاس ساده‌تر که فقط داده‌های مورد نیاز برنامه را دارد، نگاشت کند.
  • مخازن می‌توانند مدل‌های DAO را به کلاس‌های داده ساده‌تر، تنها با اطلاعاتی که لایه رابط کاربری به آن نیاز دارد، نگاشت کنند.
  • ViewModel می‌تواند مدل‌های لایه داده را در کلاس‌های UiState بگنجاند.

قراردادهای نامگذاری

هنگام نامگذاری پایگاه کد خود، باید از بهترین شیوه‌های زیر آگاه باشید:

توصیه توضیحات
روش‌های نامگذاری.
اختیاری
از عبارات فعلی برای نامگذاری متدها استفاده کنید - برای مثال، makePayment() .
ویژگی‌های نامگذاری.
اختیاری
از عبارات اسمی برای نامگذاری ویژگی‌ها استفاده کنید - برای مثال، inProgressTopicSelection .
نامگذاری جریان‌های داده
اختیاری
وقتی یک کلاس، یک جریان Flow یا هر جریان دیگری را در معرض نمایش قرار می‌دهد، قرارداد نامگذاری get{model}Stream است. برای مثال، getAuthorStream(): Flow<Author> . اگر تابع لیستی از مدل‌ها را برمی‌گرداند، از نام مدل جمع استفاده کنید: getAuthorsStream(): Flow<List<Author>> .
نامگذاری پیاده‌سازی‌های رابط‌ها
اختیاری
از نام‌های معنادار برای پیاده‌سازی رابط‌ها استفاده کنید. اگر نام بهتری پیدا نشد، Default به عنوان پیشوند استفاده کنید. برای مثال، برای یک رابط NewsRepository ، ممکن است OfflineFirstNewsRepository یا InMemoryNewsRepository داشته باشید. اگر نام خوبی پیدا نکردید، DefaultNewsRepository استفاده کنید. پیاده‌سازی‌های جعلی را با Fake پیشوند کنید، مانند FakeAuthorsRepository .

منابع اضافی

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

مستندات

محتوا را مشاهده می‌کند