Android MVVM View 모델에서 컨텍스트를 가져오는 방법
안드로이드 앱에 MVVM 패턴을 구현하려고 합니다.테스트를 쉽게 하기 위해 ViewModels에 Android 특정 코드가 포함되지 않아야 한다는 것을 읽었지만, 다양한 작업(xml에서 리소스 가져오기, 기본 설정 초기화 등)을 위해 컨텍스트를 사용해야 합니다.이것을 하는 가장 좋은 방법은 무엇입니까?는 그 난그봤어걸▁that를 보았습니다.AndroidViewModel
응용 프로그램 컨텍스트에 대한 참조가 있지만, Android별 코드가 포함되어 있기 때문에 View Model에 포함되어야 하는지 모르겠습니다.또한 활동 수명 주기 이벤트와 관련이 있지만 구성 요소의 범위를 관리하기 위해 단검을 사용하고 있기 때문에 어떤 영향을 미칠지 잘 모르겠습니다.MVVM 패턴과 단검은 처음이라 어떤 도움이든 감사합니다!
Android 아키텍처 구성 요소 뷰 모델의 경우,
편집 1: 활동 컨텍스트를 활동 보기 모델에 메모리 누수로 전달하는 것은 좋은 방법이 아닙니다.코드를 작성하는 더 나은 방법이 있고 Android View Model을 사용하여 UI에 대한 참조를 관리하는 더 나은 방법이 있으므로 이러한 방식으로 컨텍스트가 필요하지 않습니다.
따라서 View Model에서 컨텍스트를 얻으려면 View Model 클래스가 Android View Model 클래스를 확장해야 합니다.그러면 아래 예제 코드에 나와 있는 것처럼 컨텍스트를 얻을 수 있습니다.
class ActivityViewModel(application: Application) : AndroidViewModel(application) {
private val context = getApplication<Application>().applicationContext
//... ViewModel methods
}
사용할 수 있습니다.Application
에서 AndroidViewModel
▁extend를 확장해야 합니다.AndroidViewModel
그것은 단순히.ViewModel
을 Application
언급.
ViewModels에 테스트를 더 쉽게 하기 위해 Android 특정 코드가 포함되어 있지 않아야 하는 것은 아닙니다. 이는 테스트를 더 쉽게 하는 추상화이기 때문입니다.
ViewModel에 Context 인스턴스나 컨텍스트를 유지하는 View 또는 기타 개체를 포함할 수 없는 이유는 작업 및 조각과 별도의 수명 주기가 있기 때문입니다.
제가 의미하는 바는, 당신이 당신의 앱에서 회전 변경을 한다고 가정해보자는 것입니다.이렇게 하면 활동 및 조각이 자체적으로 파괴되어 자체적으로 재생성됩니다.ViewModel은 이 상태에서 유지되므로 삭제된 활동에 대한 보기 또는 컨텍스트를 계속 보유하고 있으면 충돌 및 기타 예외가 발생할 수 있습니다.
원하는 작업을 수행하는 방법에 대해서는 MVVM 및 View Model이 JetPack의 Databinding 구성 요소와 매우 잘 작동합니다.일반적으로 String, int 등을 저장하는 대부분의 경우 Databinding을 사용하여 뷰에 직접 표시할 수 있으므로 View Model 내에 값을 저장할 필요가 없습니다.
그러나 데이터 바인딩을 원하지 않는 경우에도 생성자 또는 메소드 내에서 컨텍스트를 전달하여 리소스에 액세스할 수 있습니다.View Model 내에 해당 컨텍스트의 인스턴스를 저장하지 마십시오.
View 모델에서 직접 컨텍스트를 사용하는 대신 필요한 리소스를 제공하는 ResourceProvider와 같은 공급자 클래스를 만들고 이러한 공급자 클래스를 View 모델에 주입했습니다.
간단한 대답 - 수행 안 함
왜요?
뷰 모델의 전체 목적을 충족하지 못합니다.
View 모델에서 수행할 수 있는 거의 모든 작업은 LiveData 인스턴스와 다양한 권장 접근 방식을 사용하여 작업/분절로 수행할 수 있습니다.
다른 사람들이 언급했듯이, 다음과 같은 것들이 있습니다.AndroidViewModel
이 앱을 할 수 있는 입니다.Context
하지만 제가 댓글로 수집한 바로는, 당신은 당신을 조종하려고 합니다.@drawable
당신의 내부에서 sViewModel
목적 MVVM을 무력화합니다.
반적으로, 요한것은을 .Context
의 신의에ViewModel
거의 보편적으로 당신이 논리를 나누는 방법을 재고해야 한다고 제안합니다.View
모래땅ViewModels
.
가지고 있는 대신에ViewModel
합니다. " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "" " " " " " " " " " " " " " " " " " " " "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "ViewModel
를 들어,합니다. 상태를 'drawable입니다.ViewModel
상태는 로) , 이는 (부울 상태)입니다.View
그에 따라 추첨 대상을 선택하는 것이 중요합니다.
Data Binding을 사용하면 다음과 같은 이점을 얻을 수 있습니다.
<ImageView
...
app:src="@{viewModel.isOn ? @drawable/switch_on : @drawable/switch_off}"
/>
만약 당신이 더 많은 상태와 그림을 가지고 있다면, 당신은 레이아웃 파일에서 다루기 힘든 논리를 피하기 위해, 예를 들어, 다음과 같은 것을 번역하는 사용자 정의 BindingAdapter를 작성할 수 있습니다.Enum
을 에를가두로 합니다.R.drawable.*
예: 참조):
enum class CatType { NYAN, GRUMPY, LOL }
class CatViewModel {
val catType: LiveData<CatType> = ...
// View-tier logic, takes the burden of knowing
// Contexts and R.** refs from the ViewModel
@BindingAdapter("bindCatImage")
fun bindCatImage(view: ImageView, catType: CatType) = view.apply {
val resource = when (value) {
CatType.NYAN -> R.drawable.cat_nyan
CatType.GRUMPY -> R.drawable.cat_grumpy
CatType.LOL -> R.drawable.cat_lol
}
setImageResource(resource)
}
<ImageView
bindCatType="@{vm.catType}"
... />
이 필한경이 하다면.Context
내부에서 사용하는 일부 구성 요소의 경우ViewModel
그런 다음 외부에 구성 요소를 생성합니다.ViewModel
그리고 그것을 전달합니다.하거나 DI를 할 수 . 또는 다음을 생성할 수 있습니다.Context
-dependent componentViewModel
Fragment
/Activity
.
왜 그러십니까?
Context
으로, Android에서는 다릅니다.ViewModel
테스트에 s를 할 수 ).AndroidJunitRunner
안드로이드에 특화된 것들을 위해, 하지만 추가적인 의존성 없이 더 깨끗한 코드를 갖는 것은 말이 됩니다.)당신이 의지하지 않는다면,Context
모든 것을 조롱하는 것.ViewModel
시험이 더 쉽습니다.경험의 법칙은 다음과 같습니다. View Model에서는 사용하지 마십시오. 그럴 만한 충분한 이유가 없는 한 말입니다.
TL;DR: ViewModel의 Dague를 통해 응용프로그램의 컨텍스트를 주입하고 리소스를 로드하는 데 사용합니다.이미지를 로드해야 하는 경우에는 Databinding 메서드의 인수를 통해 View 인스턴스를 전달하고 해당 View 컨텍스트를 사용합니다.
MVVM은 훌륭한 아키텍처이며 Android 개발의 미래입니다. 하지만 여전히 친환경적인 몇 가지가 있습니다.MVVM 아키텍처의 계층 통신을 예로 들면, 여러 개발자(매우 잘 알려진 개발자)가 라이브데이터를 사용하여 여러 계층을 서로 다른 방식으로 통신하는 것을 보았습니다.이들 중 일부는 LiveData를 사용하여 ViewModel을 UI와 통신하지만, 콜백 인터페이스를 사용하여 Repository와 통신하거나 Interactor/UseCase를 사용하여 LiveData를 사용하여 통신합니다.여기서 요점은, 아직 모든 것이 100% 정의된 것은 아니라는 것입니다.
즉, 귀하의 특정 문제에 대한 제 접근 방식은 DI를 통해 애플리케이션의 컨텍스트를 사용하여 ViewModel에서 String.xml과 같은 것을 가져오는 것입니다.
이미지 로드를 처리하는 경우에는 Databinding 어댑터 메서드에서 View 개체를 통과하고 View의 컨텍스트를 사용하여 이미지를 로드하려고 합니다. 왜죠? 응용 프로그램의 컨텍스트를 사용하여 이미지를 로드하면 일부 기술(예: Glide)에서 문제가 발생할 수 있기 때문입니다.
도움이 되길 바랍니다!
힐트에서:
@Inject constructor(@ApplicationContext context : Context)
응용프로그램 컨텍스트에 대한 참조가 있지만, Android 특정 코드가 포함되어 있습니다.
요, 좋은소다니입식다를 사용할 수 . 사용할 수 있습니다.Mockito.mock(Context.class)
그리고 테스트에서 원하는 대로 컨텍스트를 반환합니다!
그래서 그냥 사용합니다.ViewModel
View Model Providers를 통해 응용프로그램 컨텍스트를 제공합니다.보통 때처럼 공장에서 일하죠.
이것은 컨텍스트를 View Model로 가져오는 방법입니다.
private val context = getApplication<Application>().applicationContext
ViewModel을 사용하는 동기는 Java 코드와 Android 코드를 분리하여 비즈니스 로직을 개별적으로 테스트할 수 있기 때문에 ViewModel에서 Android 관련 개체를 사용해서는 안 됩니다. 그러면 Android 구성 요소와 비즈니스 로직 및 데이터가 별도로 계층화됩니다.충돌로 이어질 수 있으므로 View 모델에 컨텍스트가 없어야 합니다.
저는 그것을 얻는 데 어려움을 겪었습니다.SharedPreferences
를 할 때ViewModel
그래서 저는 위의 답변들로부터 조언을 받았고 다음을 사용하여 했습니다.AndroidViewModel
것이 좋아 .
의 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>"));
}
}
리고에서.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;
}
}
힐트 사용
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
@Singleton
@Provides
fun provideContext(application: Application): Context = application.applicationContext
}
그런 다음 생성자를 통해 전달합니다.
class MyRepository @Inject constructor(private val context: Context) {
...
}
프로그램 는 에서응컨액수있습다에서 수 있습니다.getApplication().getApplicationContext()
모델 보기모델) 내에서.리소스, 기본 설정 등에 액세스하는 데 필요한 작업입니다.
마지막으로 MVVM을 사용하여 viewModel에서 컨텍스트를 얻는 가장 쉬운 방법을 찾았습니다.ViewModel을 사용하는 대신 종속성 주입 또는 Android_VIEW_MODEL을 사용할 수 있도록 ViewModel 클래스의 컨텍스트가 필요하다고 가정합니다. 샘플은 아래와 같습니다.
class SampleViewModel(app: Application) : AndroidViewModel(app){
private val context = getApplication<Application>().applicationContext
val prefManager = PrefManager(context)
//Now we can call any method which is in PrefManager class like
prefManager.getToken()
}
다음 패턴을 사용합니다.
class NameViewModel(
val variable:Class,application: Application):AndroidViewModel(application){
body...
}
View 모델에 컨텍스트를 주입할 때의 문제는 화면 회전, 야간 모드 또는 시스템 언어에 따라 컨텍스트가 언제든지 변경될 수 있으며 반환되는 리소스도 그에 따라 변경될 수 있다는 것입니다.단순 리소스 ID를 반환하면 getString 대체와 같은 추가 매개 변수에 문제가 발생합니다.높은 수준의 결과를 반환하고 렌더링 로직을 활동으로 이동하면 테스트하기가 더 어려워집니다.
View Model이 나중에 활동의 컨텍스트를 통해 실행되는 함수를 생성하고 반환하도록 하는 것이 해결책입니다.코틀린의 통사 설탕은 이것을 믿을 수 없을 정도로 쉽게 만듭니다!
ViewModel.kt:
// connectedStatus holds a function that calls Context methods
// `this` can be elided
val connectedStatus = MutableLiveData<Context.() -> String> {
// initial value
this.getString(R.string.connectionStatusWaiting)
}
connectedStatus.postValue {
this.getString(R.string.connectionStatusConnected, brand)
}
Activity.kt // is a Context
override fun onCreate(_: Bundle?) {
connectionViewModel.connectedStatus.observe(this) { it ->
// runs the posted value with the given Context receiver
txtConnectionStatus.text = this.run(it)
}
}
이를 통해 ViewModel은 단위 테스트를 통해 확인된 표시된 정보를 계산하기 위한 모든 논리를 보유할 수 있습니다. 활동은 버그를 숨기기 위한 내부 논리가 없는 매우 단순한 표현입니다.
Android 문서에는 일부 다른 사용자가 지적한 것처럼 보기 모델에 컨텍스트에 대한 참조가 포함되지 않도록 경고하는 모범 사례가 있습니다.
잠재적으로 ViewModelStoreOwner보다 오래 사용할 수 있으므로 ViewModel은 메모리 누수를 방지하기 위해 컨텍스트 또는 리소스와 같은 수명 주기 관련 API에 대한 참조를 보유하지 않아야 합니다.
뷰 모델을 설계하는 방법에 대한 다른 매우 유용한 정보도 같은 페이지에 나와 있습니다.
https://developer.android.com/topic/libraries/architecture/viewmodel#best-practices
다음과 같은 방법으로 작성했습니다.
@Module
public class ContextModule {
@Singleton
@Provides
@Named("AppContext")
public Context provideContext(Application application) {
return application.getApplicationContext();
}
}
그런 다음 AppComponent에 ContextModule.class를 추가했습니다.
@Component(
modules = {
...
ContextModule.class
}
)
public interface AppComponent extends AndroidInjector<BaseApplication> {
.....
}
그런 다음 컨텍스트를 View Model에 삽입했습니다.
@Inject
@Named("AppContext")
Context context;
언급URL : https://stackoverflow.com/questions/51451819/how-to-get-context-in-android-mvvm-viewmodel
'programing' 카테고리의 다른 글
선택하지 않도록 jQuery의 라디오 버튼을 재설정하는 방법 (0) | 2023.08.02 |
---|---|
Spring Api-Gateway : (M1) java.lang.불만족스러운 링크 오류: netty_resolver_dns_native_macos_aarch_64 없음 (0) | 2023.08.02 |
배열에서 중복 항목의 발생을 계산하는 방법 (0) | 2023.08.02 |
물살이.NET SDK는 대상을 지원하지 않습니다.NET Core 3.0 | 3.1 | 5.0 | 6.0 (0) | 2023.08.02 |
배열에서 가장 큰 값의 반환 인덱스 (0) | 2023.08.02 |