Saya mencoba menerapkan pola MVVM di aplikasi android saya. Saya telah membaca bahwa ViewModels seharusnya tidak berisi kode khusus android (untuk mempermudah pengujian), namun saya perlu menggunakan konteks untuk berbagai hal (mendapatkan sumber daya dari xml, menginisialisasi preferensi, dll). Apa cara terbaik untuk melakukannya? Saya melihat itu AndroidViewModel
memiliki referensi ke konteks aplikasi, namun itu berisi kode khusus android jadi saya tidak yakin apakah itu harus ada di ViewModel. Juga yang terkait dengan peristiwa siklus hidup Aktivitas, tetapi saya menggunakan dagger untuk mengelola cakupan komponen jadi saya tidak yakin bagaimana hal itu akan mempengaruhinya. Saya baru mengenal pola MVVM dan Dagger sehingga bantuan apa pun sangat kami hargai!
sumber
AndroidViewModel
tetapi mendapatkanCannot create instance exception
maka Anda dapat merujuk ke jawaban saya ini stackoverflow.com/a/62626408/1055241Jawaban:
Anda dapat menggunakan
Application
konteks yang disediakan olehAndroidViewModel
, Anda harus memperpanjangAndroidViewModel
yang hanyaViewModel
mencakupApplication
referensi.sumber
Untuk Model Tampilan Komponen Arsitektur Android,
Ini bukan praktik yang baik untuk meneruskan Konteks Aktivitas Anda ke ViewModel Aktivitas karena ini adalah kebocoran memori.
Oleh karena itu untuk mendapatkan konteks dalam ViewModel Anda, kelas ViewModel harus memperluas Kelas Model Tampilan Android . Dengan begitu Anda bisa mendapatkan konteks seperti yang ditunjukkan pada kode contoh di bawah ini.
class ActivityViewModel(application: Application) : AndroidViewModel(application) { private val context = getApplication<Application>().applicationContext //... ViewModel methods }
sumber
Bukan berarti ViewModels tidak boleh berisi kode khusus Android untuk mempermudah pengujian, karena abstraksi-lah yang membuat pengujian lebih mudah.
Alasan mengapa ViewModels tidak boleh berisi instance Konteks atau apa pun seperti Tampilan atau objek lain yang memegang Konteks adalah karena ia memiliki siklus proses yang terpisah dari Aktivitas dan Fragmen.
Yang saya maksud dengan ini adalah, katakanlah Anda melakukan perubahan rotasi pada aplikasi Anda. Hal ini menyebabkan Aktivitas dan Fragmen Anda menghancurkan dirinya sendiri sehingga membuatnya kembali. ViewModel dimaksudkan untuk bertahan selama keadaan ini, jadi ada kemungkinan error dan pengecualian lain terjadi jika masih memegang View atau Konteks ke Aktivitas yang dihancurkan.
Mengenai bagaimana Anda harus melakukan apa yang ingin Anda lakukan, MVVM dan ViewModel bekerja sangat baik dengan komponen Penyatuan Data JetPack. Untuk sebagian besar hal Anda biasanya akan menyimpan String, int, atau dll, Anda dapat menggunakan Databinding untuk membuat Tampilan menampilkannya secara langsung, sehingga tidak perlu menyimpan nilai di dalam ViewModel.
Tetapi jika Anda tidak menginginkan Penyatuan Data, Anda masih bisa meneruskan Konteks di dalam konstruktor atau metode untuk mengakses Sumber Daya. Hanya saja, jangan memegang contoh Konteks itu di dalam ViewModel Anda.
sumber
Jawaban singkat - Jangan lakukan ini
Kenapa?
Ini mengalahkan seluruh tujuan model tampilan
Hampir semua yang dapat Anda lakukan dalam model tampilan dapat dilakukan dalam aktivitas / fragmen dengan menggunakan instance LiveData dan berbagai pendekatan lain yang direkomendasikan.
sumber
Apa yang akhirnya saya lakukan alih-alih memiliki Konteks langsung di ViewModel, saya membuat kelas penyedia seperti ResourceProvider yang akan memberi saya sumber daya yang saya butuhkan, dan saya meminta kelas penyedia tersebut dimasukkan ke dalam ViewModel saya
sumber
getDrawableRes(@DrawableRes int id)
di dalam kelas ResourceProviderTL; DR: Masukkan konteks Aplikasi melalui Dagger di ViewModels Anda dan gunakan untuk memuat sumber daya. Jika Anda perlu memuat gambar, teruskan instance View melalui argumen dari metode Databinding dan gunakan konteks View tersebut.
MVVM adalah arsitektur yang baik dan jelas merupakan masa depan pengembangan Android, tetapi ada beberapa hal yang masih ramah lingkungan. Ambil contoh komunikasi lapisan dalam arsitektur MVVM, saya telah melihat pengembang yang berbeda (pengembang yang sangat terkenal) menggunakan LiveData untuk mengkomunikasikan lapisan yang berbeda dengan cara yang berbeda. Beberapa dari mereka menggunakan LiveData untuk mengkomunikasikan ViewModel dengan UI, tetapi kemudian mereka menggunakan antarmuka callback untuk berkomunikasi dengan Repositori, atau mereka memiliki Interactors / UseCases dan mereka menggunakan LiveData untuk berkomunikasi dengan mereka. Titik di sini, adalah bahwa tidak semuanya 100% mendefinisikan belum .
Karena itu, pendekatan saya dengan masalah spesifik Anda adalah memiliki konteks Aplikasi yang tersedia melalui DI untuk digunakan di ViewModels saya untuk mendapatkan hal-hal seperti String dari strings.xml saya
Jika saya berurusan dengan pemuatan gambar, saya mencoba melewati objek View dari metode adaptor Databinding dan menggunakan konteks View untuk memuat gambar. Mengapa? karena beberapa teknologi (misalnya Glide) dapat mengalami masalah jika Anda menggunakan konteks Aplikasi untuk memuat gambar.
Semoga membantu!
sumber
Seperti yang telah disebutkan orang lain, ada
AndroidViewModel
yang dapat Anda peroleh dari untuk mendapatkan aplikasiContext
tetapi dari apa yang saya kumpulkan di komentar, Anda mencoba memanipulasi@drawable
dari dalam AndaViewModel
yang mengalahkan tujuan MVVM.Secara umum, kebutuhan untuk memiliki a
Context
dalam AndaViewModel
hampir secara universal menyarankan agar Anda mempertimbangkan untuk memikirkan kembali bagaimana Anda membagi logika antaraView
s danViewModels
.Daripada
ViewModel
menyelesaikan drawable dan memasukkannya ke Aktivitas / Fragmen, pertimbangkan untuk meminta Fragmen / Aktivitas menyulap drawable berdasarkan data yang dimiliki olehViewModel
. Misalnya, Anda memerlukan drawable yang berbeda untuk ditampilkan dalam tampilan untuk status aktif / nonaktif - inilahViewModel
yang harus menahan status (mungkin boolean), tetapiView
tugasnya adalah untuk memilih drawable yang sesuai.Ini dapat dilakukan dengan cukup mudah dengan DataBinding :
<ImageView ... app:src="@{viewModel.isOn ? @drawable/switch_on : @drawable/switch_off}" />
Jika Anda memiliki lebih banyak status dan sumber daya dapat digambar, untuk menghindari logika yang tidak berguna dalam file tata letak, Anda dapat menulis BindingAdapter khusus yang menerjemahkan, katakanlah, sebuah
Enum
nilai ke dalamR.drawable.*
(mis. Setelan kartu)Atau mungkin Anda memerlukan
Context
untuk beberapa komponen yang Anda gunakan di dalam AndaViewModel
- kemudian, buat komponen di luarViewModel
dan kirimkan. Anda dapat menggunakan DI, atau singletons, atau membuatContext
komponen -dependen tepat sebelum inisialisasiViewModel
inFragment
/Activity
.Mengapa repot:
Context
adalah hal khusus Android, dan bergantung pada merekaViewModel
adalah praktik yang buruk: mereka menghalangi pengujian unit. Di sisi lain, komponen / antarmuka layanan Anda sendiri sepenuhnya di bawah kendali Anda sehingga Anda dapat dengan mudah mengejeknya untuk pengujian.sumber
Kabar baiknya, Anda dapat menggunakan
Mockito.mock(Context.class)
dan membuat konteks mengembalikan apa pun yang Anda inginkan dalam pengujian!Jadi gunakan saja
ViewModel
seperti yang biasa Anda lakukan, dan berikan ApplicationContext melalui ViewModelProviders.Factory seperti biasa.sumber
Anda dapat mengakses konteks aplikasi
getApplication().getApplicationContext()
dari dalam ViewModel. Inilah yang Anda butuhkan untuk mengakses sumber daya, preferensi, dll ..sumber
ViewModel
kelas tidak memilikigetApplication
metode.AndroidViewModel
tidakAnda tidak boleh menggunakan objek yang berhubungan dengan Android dalam ViewModel karena motif penggunaan ViewModel adalah untuk memisahkan kode java dan kode Android sehingga Anda dapat menguji logika bisnis secara terpisah dan Anda akan memiliki lapisan terpisah dari komponen Android dan logika bisnis Anda. dan data, Anda tidak boleh memiliki konteks di ViewModel karena dapat menyebabkan error
sumber
Saya mengalami kesulitan
SharedPreferences
saat menggunakanViewModel
kelas jadi saya mengambil saran dari jawaban di atas dan melakukan yang berikut menggunakanAndroidViewModel
. Semuanya terlihat bagus sekarangUntuk
AndroidViewModel
import android.app.Application; import android.content.Context; import android.content.SharedPreferences; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.preference.PreferenceManager; public class HomeViewModel extends AndroidViewModel { private MutableLiveData<String> some_string; public HomeViewModel(Application application) { super(application); some_string = new MutableLiveData<>(); Context context = getApplication().getApplicationContext(); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); some_string.setValue("<your value here>")); } }
Dan di
Fragment
import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProviders; public class HomeFragment extends Fragment { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View root = inflater.inflate(R.layout.fragment_home, container, false); HomeViewModel homeViewModel = ViewModelProviders.of(this).get(HomeViewModel.class); homeViewModel.getAddress().observe(getViewLifecycleOwner(), new Observer<String>() { @Override public void onChanged(@Nullable String address) { } }); return root; } }
sumber
Saya membuatnya seperti ini:
Dan kemudian saya baru saja menambahkan di AppComponent ContextModule.class:
@Component( modules = { ... ContextModule.class } ) public interface AppComponent extends AndroidInjector<BaseApplication> { ..... }
Dan kemudian saya memasukkan konteks di ViewModel saya:
sumber
Gunakan pola berikut:
sumber