
حتى لو لم ترتكب هذه الأخطاء ، يجدر تذكرها حتى لا تواجه بعض المشكلات في المستقبل.
1. تسرب المراقبين LiveData في شظايا
الشظايا لها دورة حياة معقدة ، وعندما تفصل شظية ما وتعاود نشاطها ، لا يتم تدميرها دائمًا. على سبيل المثال ، لا يتم إتلاف الأجزاء المحفوظة أثناء تغييرات التكوين. عند حدوث ذلك ، يبقى مثيل الجزء ، ويتم تدمير طريقة العرض الخاصة به فقط ، لذلك لا onDestroy()
استدعاء onDestroy()
ولا يتم الوصول إلى حالة المدمرة.
هذا يعني أننا إذا بدأنا بمراقبة LiveData في onCreateView()
أو ما بعده (عادة في onActivityCreated()
) وتمرير الجزء كـ LifecycleOwner:
class BooksFragment: Fragment() { private lateinit var viewModel: BooksViewModel override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_books, container) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) viewModel = ViewModelProviders.of(this).get(BooksViewModel::class.java) viewModel.liveData.observe(this, Observer { updateViews(it) })
ثم سنقوم بنقل نسخة مماثلة جديدة من الأوبزرفر في كل مرة يعيد فيها انضمام جزء ، لكن LiveData لن تحذف المراقبين السابقين لأن LifecycleOwner (جزء) لم يصل إلى حالة DESTROYED. هذا يؤدي في النهاية إلى عدد متزايد من المراقبين متطابقة ونشطة في وقت واحد ، ويتم تنفيذ نفس الرمز من onChanged()
عدة مرات.
تم الإبلاغ عن المشكلة في الأصل هنا ، ويمكن العثور على شرح أكثر تفصيلًا هنا .
الحل الموصى به هو استخدام getViewLifecycleOwner () أو getViewLifecycleOwnerLiveData () من دورة حياة الأجزاء ، والتي تمت إضافتها إلى مكتبة الدعم 28.0.0 و AndroidX 1.0.0 ، بحيث يحذف LiveData المراقبين في كل مرة يتم فيها تدمير جزء العرض:
class BooksFragment : Fragment() { ... override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) viewModel = ViewModelProviders.of(this).get(BooksViewModel::class.java) viewModel.liveData.observe(viewLifecycleOwner, Observer { updateViews(it) }) } ... }
2. إعادة تشغيل البيانات بعد كل دوران الشاشة
لقد اعتدنا على وضع منطق التهيئة والتكوين في onCreate()
في النشاط (والقياس في onCreateView()
أو في وقت لاحق في أجزاء) ، لذلك في هذه المرحلة قد يكون من المغري الشروع في تحميل بعض البيانات في ViewModels. ومع ذلك ، وفقًا لمنطقك ، يمكن أن يؤدي ذلك إلى إعادة تحميل البيانات بعد كل دورة من دوران الشاشة (حتى لو تم استخدام ViewModel) ، وهو أمر لا معنى له في معظم الحالات.
مثال:
class ProductViewModel( private val repository: ProductRepository ) : ViewModel() { private val productDetails = MutableLiveData<Resource<ProductDetails>>() private val specialOffers = MutableLiveData<Resource<SpecialOffers>>() fun getProductsDetails(): LiveData<Resource<ProductDetails>> { repository.getProductDetails()
القرار يعتمد أيضا على المنطق الخاص بك. إذا كان المستودع سيقوم بتخزين البيانات مؤقتًا ، فمن المحتمل أن يعمل الرمز أعلاه بشكل صحيح. أيضًا ، يمكن حل هذه المشكلة بطرق أخرى:
- استخدم شيئًا مشابهًا لـ AbsentLiveData وابدأ التنزيل فقط في حالة عدم تلقي البيانات ؛
- ابدأ في تنزيل البيانات عندما يكون ذلك ضروريًا حقًا. على سبيل المثال ، في OnClickListener ؛
- وربما أبسط: إجراء مكالمات تحميل في مُنشئ ViewModel واستخدام getters بسيطة:
class ProductViewModel( private val repository: ProductRepository ) : ViewModel() { private val productDetails = MutableLiveData<Resource<ProductDetails>>() private val specialOffers = MutableLiveData<Resource<SpecialOffers>>() init { loadProductsDetails()
3. تسرب ViewModels
أوضح مطورو العمارة أننا يجب ألا نمرر روابط مشاهدة إلى ViewModel.

ولكن يجب علينا أيضًا توخي الحذر عند تمرير الإشارات إلى ViewModels إلى الفئات الأخرى. بعد اكتمال النشاط (أو الجزء) ، لا يمكن الرجوع إلى ViewModel إلى أي كائن يمكن للنشاط البقاء على قيد الحياة ، بحيث يمكن تدمير ViewModel بواسطة أداة تجميع مجمعي البيانات المهملة.
في مثال التسرب هذا ، يتم تمرير وحدة إصغاء المستودع ، المكتوبة بأسلوب Singleton ، إلى ViewModel. بعد ذلك ، لا يتم إتلاف المستمع:
@Singleton class LocationRepository() { private var listener: ((Location) -> Unit)? = null fun setOnLocationChangedListener(listener: (Location) -> Unit) { this.listener = listener } private fun onLocationUpdated(location: Location) { listener?.invoke(location) } } class MapViewModel: AutoClearViewModel() { private val liveData = MutableLiveData<LocationRepository.Location>() private val repository = LocationRepository() init { repository.setOnLocationChangedListener { liveData.value = it } } }
قد يكون الحل هنا هو إزالة المستمع في طريقة onCleared()
أو حفظه كـ WeakReference
في المستودع أو استخدام LiveData للاتصال بين المستودع و ViewModel:
@Singleton class LocationRepository() { private var listener: ((Location) -> Unit)? = null fun setOnLocationChangedListener(listener: (Location) -> Unit) { this.listener = listener } fun removeOnLocationChangedListener() { this.listener = null } private fun onLocationUpdated(location: Location) { listener?.invoke(location) } } class MapViewModel: AutoClearViewModel() { private val liveData = MutableLiveData<LocationRepository.Location>() private val repository = LocationRepository() init { repository.setOnLocationChangedListener { liveData.value = it } } override onCleared() { repository.removeOnLocationChangedListener() } }
4. السماح لعرض لتعديل LiveData
هذا ليس خطأ ، لكنه يتعارض مع الفصل بين المصالح. عرض - الأجزاء والنشاط - لا ينبغي أن يكون قادرًا على تحديث LiveData وبالتالي حالته الخاصة ، لأنه مسؤول عن ViewModels. يجب أن يكون العرض قادرًا على مشاهدة LiveData فقط.
لذلك ، يجب علينا تغليف الوصول إلى MutableLiveData:
class CatalogueViewModel : ViewModel() {
5. إنشاء تبعيات ViewModel بعد كل تغيير التكوين
يتحمل ViewModels تغييرات التكوين ، مثل تدوير الشاشة ، لذلك إذا قمت بإنشائها في كل مرة يحدث فيها تغيير ، فقد يؤدي ذلك أحيانًا إلى سلوك غير متوقع ، خاصةً إذا تم تضمين بعض المنطق في المصمم.
على الرغم من أن هذا قد يبدو واضحًا إلى حد ما ، إلا أنه من السهل التغاضي عنه عند استخدام ViewModelFactory ، والذي عادةً ما يكون له نفس التبعيات التي يتمتع بها ViewModel.
ViewModelProvider يحفظ مثيل ViewModel ، ولكن ليس مثيل ViewModelFactory ، لذلك إذا كان لدينا هذا الرمز:
class MoviesViewModel( private val repository: MoviesRepository, private val stringProvider: StringProvider, private val authorisationService: AuthorisationService ) : ViewModel() { ... } class MoviesViewModelFactory( private val repository: MoviesRepository, private val stringProvider: StringProvider, private val authorisationService: AuthorisationService ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { return MoviesViewModel(repository, stringProvider, authorisationService) as T } } class MoviesActivity : AppCompatActivity() { @Inject lateinit var viewModelFactory: MoviesViewModelFactory private lateinit var viewModel: MoviesViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_movies) injectDependencies() viewModel = ViewModelProviders.of(this, viewModelFactory).get(MoviesViewModel::class.java) } ... }
ثم في كل مرة يحدث تغيير في التكوين ، سنقوم بإنشاء مثيل جديد من ViewModelFactory ، وبالتالي ، دون الحاجة الخاصة لإنشاء مثيلات جديدة لجميع التبعيات (بشرط ألا تكون محدودة بأي شكل من الأشكال).
class MoviesViewModel( private val repository: MoviesRepository, private val stringProvider: StringProvider, private val authorisationService: AuthorisationService ) : ViewModel() { ... } class MoviesViewModelFactory( private val repository: Provider<MoviesRepository>, private val stringProvider: Provider<StringProvider>, private val authorisationService: Provider<AuthorisationService> ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { return MoviesViewModel(repository.get(), stringProvider.get(), authorisationService.get() ) as T } } class MoviesActivity : AppCompatActivity() { @Inject lateinit var viewModelFactory: MoviesViewModelFactory private lateinit var viewModel: MoviesViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_movies) injectDependencies() viewModel = ViewModelProviders.of(this, viewModelFactory).get(MoviesViewModel::class.java) } ... }