programing

Android MVVM View 모델에서 컨텍스트를 가져오는 방법

javajsp 2023. 8. 2. 08:47

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그것은 단순히.ViewModelApplication언급.

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 componentViewModelFragment/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)그리고 테스트에서 원하는 대로 컨텍스트를 반환합니다!

그래서 그냥 사용합니다.ViewModelView 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