[android] Android ViewModel 추가 인수

AndroidViewModel응용 프로그램 컨텍스트를 제외하고 내 사용자 지정 생성자에 추가 인수를 전달하는 방법이 있습니까? 예:

public class MyViewModel extends AndroidViewModel {
    private final LiveData<List<MyObject>> myObjectList;
    private AppDatabase appDatabase;

    public MyViewModel(Application application, String param) {
        super(application);
        appDatabase = AppDatabase.getDatabase(this.getApplication());

        myObjectList = appDatabase.myOjectModel().getMyObjectByParam(param);
    }
}

그리고 내 커스텀 ViewModel클래스 를 사용하고 싶을 때이 코드를 내 조각에 사용합니다.

MyViewModel myViewModel = ViewModelProvider.of(this).get(MyViewModel.class)

그래서 추가 인수 String param를 내 custom 에 전달하는 방법을 모르겠습니다 ViewModel. 애플리케이션 컨텍스트 만 전달할 수 있지만 추가 인수는 전달할 수 없습니다. 도움을 주시면 감사하겠습니다. 감사합니다.

편집 : 일부 코드를 추가했습니다. 이제 더 나아 졌으면합니다.



답변

ViewModel에 대한 팩토리 클래스가 있어야합니다.

public class MyViewModelFactory implements ViewModelProvider.Factory {
    private Application mApplication;
    private String mParam;


    public MyViewModelFactory(Application application, String param) {
        mApplication = application;
        mParam = param;
    }


    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        return (T) new MyViewModel(mApplication, mParam);
    }
}

뷰 모델을 인스턴스화 할 때 다음과 같이합니다.

MyViewModel myViewModel = ViewModelProvider(this, new MyViewModelFactory(this.getApplication(), "my awesome param")).get(MyViewModel.class);

kotlin의 경우 위임 된 속성을 사용할 수 있습니다.

val viewModel: MyViewModel by viewModels { MyViewModelFactory(getApplication(), "my awesome param") }

또 다른 새로운 옵션 이 있습니다. 팩토리의 인스턴스화 를 구현 HasDefaultViewModelProviderFactory하고 재정의 getDefaultViewModelProviderFactory()한 다음 팩토리를 호출 ViewModelProvider(this)하거나 호출 by viewModels()하지 않는 것 입니다.


답변

종속성 주입으로 구현

이것은 프로덕션 코드에 대해 더 발전되고 더 좋습니다.

Square의 AssistedInject 인 Dagger2 는 네트워크 및 데이터베이스 요청을 처리하는 리포지토리와 같은 필수 구성 요소를 삽입 할 수있는 ViewModels에 대한 프로덕션 준비 구현을 제공합니다. 또한 활동 / 단편에 인수 / 매개 변수를 수동으로 삽입 할 수 있습니다. 다음 은 Gabor Varadi의 자세한 게시물 인 Dagger Tips를 기반으로 코드 요점으로 구현 하는 단계에 대한 간결한 개요입니다 .

Dagger Hilt는 20 년 7 월 12 일 알파 버전의 차세대 솔루션으로, 라이브러리가 릴리스 상태에 있으면 더 간단한 설정으로 동일한 사용 사례를 제공합니다.

Kotlin에서 Lifecycle 2.2.0 으로 구현

인수 / 매개 변수 전달

// Override ViewModelProvider.NewInstanceFactory to create the ViewModel (VM).
class SomeViewModelFactory(private val someString: String): ViewModelProvider.NewInstanceFactory() {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T = SomeViewModel(someString) as T
} 

class SomeViewModel(private val someString: String) : ViewModel() {
    init {
        //TODO: Use 'someString' to init process when VM is created. i.e. Get data request.
    }
}

class Fragment: Fragment() {
    // Create VM in activity/fragment with VM factory.
    val someViewModel: SomeViewModel by viewModels { SomeViewModelFactory("someString") } 
}

인수 / 매개 변수로 SavedState 활성화

class SomeViewModelFactory(
        private val owner: SavedStateRegistryOwner,
        private val someString: String) : AbstractSavedStateViewModelFactory(owner, null) {
    override fun <T : ViewModel?> create(key: String, modelClass: Class<T>, state: SavedStateHandle) =
            SomeViewModel(state, someString) as T
}

class SomeViewModel(private val state: SavedStateHandle, private val someString: String) : ViewModel() {
    val feedPosition = state.get<Int>(FEED_POSITION_KEY).let { position ->
        if (position == null) 0 else position
    }
        
    init {
        //TODO: Use 'someString' to init process when VM is created. i.e. Get data request.
    }
        
     fun saveFeedPosition(position: Int) {
        state.set(FEED_POSITION_KEY, position)
    }
}

class Fragment: Fragment() {
    // Create VM in activity/fragment with VM factory.
    val someViewModel: SomeViewModel by viewModels { SomeViewModelFactory(this, "someString") } 
    private var feedPosition: Int = 0
     
    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        someViewModel.saveFeedPosition((contentRecyclerView.layoutManager as LinearLayoutManager)
                .findFirstVisibleItemPosition())
    }    
        
    override fun onViewStateRestored(savedInstanceState: Bundle?) {
        super.onViewStateRestored(savedInstanceState)
        feedPosition = someViewModel.feedPosition
    }
}


답변

여러 다른 뷰 모델간에 공유되는 한 공장의 경우 다음과 같이 mlyko의 답변을 확장합니다.

public class MyViewModelFactory extends ViewModelProvider.NewInstanceFactory {
    private Application mApplication;
    private Object[] mParams;

    public MyViewModelFactory(Application application, Object... params) {
        mApplication = application;
        mParams = params;
    }

    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        if (modelClass == ViewModel1.class) {
            return (T) new ViewModel1(mApplication, (String) mParams[0]);
        } else if (modelClass == ViewModel2.class) {
            return (T) new ViewModel2(mApplication, (Integer) mParams[0]);
        } else if (modelClass == ViewModel3.class) {
            return (T) new ViewModel3(mApplication, (Integer) mParams[0], (String) mParams[1]);
        } else {
            return super.create(modelClass);
        }
    }
}

뷰 모델 인스턴스화 :

ViewModel1 vm1 = ViewModelProviders.of(this, new MyViewModelFactory(getApplication(), "something")).get(ViewModel1.class);
ViewModel2 vm2 = ViewModelProviders.of(this, new MyViewModelFactory(getApplication(), 123)).get(ViewModel2.class);
ViewModel3 vm3 = ViewModelProviders.of(this, new MyViewModelFactory(getApplication(), 123, "something")).get(ViewModel3.class);

다른 생성자를 가진 다른 뷰 모델.


답변

@ vilpe89를 기반으로 위의 AndroidViewModel 케이스용 Kotlin 솔루션

class ExtraParamsViewModelFactory(private val application: Application, private val myExtraParam: String): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = SomeViewModel(application, myExtraParam) as T

}

그런 다음 조각은 viewModel을 다음과 같이 시작할 수 있습니다.

class SomeFragment : Fragment() {
 ....
    private val myViewModel: SomeViewModel by viewModels {
        ExtraParamsViewModelFactory(this.requireActivity().application, "some string value")
    }
 ....
}

그리고 실제 ViewModel 클래스는

class SomeViewModel(application: Application, val myExtraParam:String) : AndroidViewModel(application) {
....
}

또는 적절한 방법으로 …

override fun onActivityCreated(...){
    ....

    val myViewModel = ViewModelProvider(this, ExtraParamsViewModelFactory(this.requireActivity().application, "some string value")).get(SomeViewModel::class.java)

    ....
}


답변

이미 생성 된 객체가 전달되는 클래스로 만들었습니다.

private Map<String, ViewModel> viewModelMap;

public ViewModelFactory() {
    this.viewModelMap = new HashMap<>();
}

public void add(ViewModel viewModel) {
    viewModelMap.put(viewModel.getClass().getCanonicalName(), viewModel);
}

@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
    for (Map.Entry<String, ViewModel> viewModel : viewModelMap.entrySet()) {
        if (viewModel.getKey().equals(modelClass.getCanonicalName())) {
            return (T) viewModel.getValue();
        }
    }
    return null;
}

그리고

ViewModelFactory viewModelFactory = new ViewModelFactory();
viewModelFactory.add(new SampleViewModel(arg1, arg2));
SampleViewModel sampleViewModel = ViewModelProviders.of(this, viewModelFactory).get(SampleViewModel.class);


답변

Dagger에서 종속성으로 제공 할 수있는 ViewModel 인수로 원활하게 작업하면서이 작업을보다 간단하고 깔끔하게 만들 수있는 라이브러리를 작성했습니다.
https://github.com/radutopor/ViewModelFactory

@ViewModelFactory
class UserViewModel(@Provided repository: Repository, userId: Int) : ViewModel() {

    val greeting = MutableLiveData<String>()

    init {
        val user = repository.getUser(userId)
        greeting.value = "Hello, $user.name"
    }    
}

보기에서 :

class UserActivity : AppCompatActivity() {
    @Inject
    lateinit var userViewModelFactory2: UserViewModelFactory2

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user)
        appComponent.inject(this)

        val userId = intent.getIntExtra("USER_ID", -1)
        val viewModel = ViewModelProviders.of(this, userViewModelFactory2.create(userId))
            .get(UserViewModel::class.java)

        viewModel.greeting.observe(this, Observer { greetingText ->
            greetingTextView.text = greetingText
        })
    }
}


답변

(KOTLIN) 내 솔루션은 약간의 반사를 사용합니다.

인수가 필요한 새 ViewModel 클래스를 만들 때마다 동일한 모양의 Factory 클래스를 만들고 싶지 않다고 가정 해 보겠습니다. Reflection을 통해이 작업을 수행 할 수 있습니다.

예를 들어 두 가지 다른 활동이 있습니다.

class Activity1 : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val args = Bundle().apply { putString("NAME_KEY", "Vilpe89") }
        val viewModel = ViewModelProviders.of(this, ViewModelWithArgumentsFactory(args))
            .get(ViewModel1::class.java)
    }
}

class Activity2 : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val args = Bundle().apply { putInt("AGE_KEY", 29) }
        val viewModel = ViewModelProviders.of(this, ViewModelWithArgumentsFactory(args))
            .get(ViewModel2::class.java)
    }
}

그리고 해당 활동에 대한 ViewModels :

class ViewModel1(private val args: Bundle) : ViewModel()

class ViewModel2(private val args: Bundle) : ViewModel()

그런 다음 마술 부분, Factory 클래스의 구현 :

class ViewModelWithArgumentsFactory(private val args: Bundle) : NewInstanceFactory() {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        try {
            val constructor: Constructor<T> = modelClass.getDeclaredConstructor(Bundle::class.java)
            return constructor.newInstance(args)
        } catch (e: Exception) {
            Timber.e(e, "Could not create new instance of class %s", modelClass.canonicalName)
            throw e
        }
    }
}