Android ViewModel附加参数

Mar*_*man 72 android mvvm viewmodel

有没有办法将其他参数传递给我的自定义AndroidViewModel构造函数,除了Application上下文.例:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

当我想要使用我的自定义ViewModel类时,我在我的片段中使用此代码:

MyViewModel myViewModel = ViewModelProvider.of(this).get(MyViewModel.class)
Run Code Online (Sandbox Code Playgroud)

所以我不知道如何将额外的参数传递String param给我的自定义ViewModel.我只能传递Application上下文,但不能传递其他参数.我真的很感激任何帮助.谢谢.

编辑:我添加了一些代码.我希望现在好多了.

mly*_*yko 151

您需要为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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在实例化视图模型时,您会这样做:

MyViewModel myViewModel = ViewModelProviders.of(this, new MyViewModelFactory(this.getApplication(), "my awesome param")).get(MyViewModel.class);
Run Code Online (Sandbox Code Playgroud)

  • 每个`ViewModel` 类都需要它的ViewModelFactory 吗? (7认同)
  • 但每个`ViewModel`都可以/将有不同的DI.你怎么知道`create()`方法返回哪个实例? (4认同)
  • 这不是真的.新的`ViewModel`创建阻止了方法`get()`.基于文档:"返回现有的ViewModel或在范围内创建一个新的ViewModel(通常是片段或活动),与此ViewModelProvider相关联." 请参阅:https://developer.android.com/reference/android/arch/lifecycle/ViewModelProvider.html#get(java.lang.Class%3CT%3E) (2认同)
  • 如何使用`return modelClass.cast(new MyViewModel(mApplication, mParam))`来摆脱警告 (2认同)

Ada*_*itz 36

使用依赖注入实现

这对于生产代码来说更先进也更好。

Dagger2,Square 的AssistedInject为 ViewModels 提供了一个生产就绪的实现,可以注入必要的组件,例如处理网络和数据库请求的存储库。它还允许在活动/片段中手动注入参数/参数。以下是基于 Gabor Varadi 的详细文章Dagger Tips使用代码 Gists实现步骤的简要概述。

Dagger Hilt是下一代解决方案,自 20 年 7 月 12 日起处于 alpha 阶段,一旦库处于发布状态,就提供了相同的用例和更简单的设置。

在 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") } 
}
Run Code Online (Sandbox Code Playgroud)

使用参数/参数启用 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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 在工厂中重写 create 时,我收到一条警告,提示 Unchecked cast 'ItemViewModel to T' (2认同)

rze*_*han 13

对于在多个不同视图模型之间共享的一个工厂,我将扩展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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

并实例化视图模型:

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);
Run Code Online (Sandbox Code Playgroud)

具有不同构造函数的不同视图模型.

  • 我不推荐这种方式,因为有几个原因:1)工厂中的参数不是类型安全的 - 这样你就可以在运行时破坏你的代码.在可能的情况下总是尽量避免这种方法2)检查视图模型类型并不是真正的OOP方式.由于ViewModel被转换为基类型,所以你可以在运行期间中断代码,而不会在编译期间发出任何警告.在这种情况下,我建议使用默认的android工厂并将参数传递给已经实例化的视图模型. (6认同)

MFA*_*FAL 6

基于@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
}
Run Code Online (Sandbox Code Playgroud)

然后片段可以启动 viewModel 作为

class SomeFragment : Fragment() {
    
    // ...

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

    // ...

}
Run Code Online (Sandbox Code Playgroud)

然后是实际的 ViewModel 类

class SomeViewModel(application: Application, val myExtraParam:String) : AndroidViewModel(application) {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

或者用一些合适的方法......

override fun onActivityCreated(...){
    // ...
    val myViewModel = ViewModelProvider(this, ExtraParamsViewModelFactory(this.requireActivity().application, "some string value")).get(SomeViewModel::class.java)
    // ...
}
Run Code Online (Sandbox Code Playgroud)


s-h*_*ter 5

正确的方法是使用依赖注入框架,例如Dagger hilt。如果不使用 DI 框架,则使用 ViewModelFactory 来完成。

带有匕首柄:

带参数的 ViewModel

@HiltViewModel
class MyViewModel @Inject constructor(
    private val myRepository: MyRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() { ... }
Run Code Online (Sandbox Code Playgroud)

一个存储库

class MyRepository @Inject constructor(
    private val myRemoteDataSource: MyDataSource,
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) { ... }
Run Code Online (Sandbox Code Playgroud)

用于提供依赖项/参数的模块,以便将它们注入到存储库和 ViewModel 中。

@InstallIn(ViewModelComponent::class)
@Module
object MyProvideModule {
    @Provides
    fun provideMyDataSource(@ApplicationContext context: Context): MyDataSource {
        //code to create MyDataSource... 
        return MyDataSource(context)
    }

    @Provides
    fun provideCoroutineDispatcher(): CoroutineDispatcher {
        return Dispatchers.IO
    }
}
Run Code Online (Sandbox Code Playgroud)

用于绑定存储库的模块

@Module
@InstallIn(ViewModelComponent::class)
interface RepositoryModules {
    @Binds
    fun provideMyRepository(repository: MyRepository): MyRepository
}
Run Code Online (Sandbox Code Playgroud)

使用带有 @HiltAndroidApp 注释的应用程序启动 Dagger hilt。

@HiltAndroidApp
class MainApplication : Application() {

    override fun onCreate() {
        super.onCreate()
    }

}
Run Code Online (Sandbox Code Playgroud)

在活动中获取 ViewModel

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
  private val myViewModel: MyViewModel by viewModels()
  // Other code...
}
Run Code Online (Sandbox Code Playgroud)

获取 Fragment 中的 ViewModel

@AndroidEntryPoint
class MyFragment : Fragment() {
  private val myViewModel: MyViewModel by activityViewModels()
  // Other code...
}
Run Code Online (Sandbox Code Playgroud)

使用 ViewModelFactory:

带有参数 messageDataStore 的 ViewModel,其中 MessageDataStore 是 DataStore 类,也可以是您想要传递到 ViewModel 中的任何其他内容。

class MyViewModel(
    private val messageDataStore: MessageDataStore,
): ViewModel() { ... }
Run Code Online (Sandbox Code Playgroud)

用于创建 ViewModel 的 ViewModel 工厂类

/**
 * Factory for all ViewModels.
 */
@Suppress("UNCHECKED_CAST")
class ViewModelFactory constructor(
    private val messageDataStore: MessageDataStore,
    owner: SavedStateRegistryOwner,
    defaultArgs: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
    override fun <T : ViewModel> create(
        key: String,
        modelClass: Class<T>,
        handle: SavedStateHandle
    ) = with(modelClass) {
        when {
            isAssignableFrom(MyViewModel::class.java) ->
                MyViewModel(messageDataStore)
            else ->
                throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
        }
    } as T
}
Run Code Online (Sandbox Code Playgroud)

用于创建依赖项/参数的应用程序类

class MyApp : Application() {
    val messageDataStore: MessageDataStore
        get() = MessageDataStore.getInstance(this)

}
Run Code Online (Sandbox Code Playgroud)

用于获取活动和片段中的工厂类的扩展函数,MyExt.kt

fun AppCompatActivity.getViewModelFactory(savedInstanceState: Bundle?): ViewModelFactory {
    val messageDataStore = (applicationContext as MyApp).messageDataStore
    return ViewModelFactory(messageDataStore, this, savedInstanceState)
}

fun Fragment.getViewModelFactory(savedInstanceState: Bundle?): ViewModelFactory {
    val messageDataStore = (requireContext().applicationContext as MyApp).messageDataStore
    return ViewModelFactory(messageDataStore, this.requireActivity(), savedInstanceState)
}
Run Code Online (Sandbox Code Playgroud)

获取 Activity 中的 ViewMode

class MainActivity : AppCompatActivity() {

  private lateinit var myViewModel: MyViewModel
  // Other code...

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val vm by viewModels<MyViewModel> { getViewModelFactory(savedInstanceState) }
    myViewModel = vm
    // Other code...
  }
}
Run Code Online (Sandbox Code Playgroud)

获取 Fragments 中的 ViewModel。

class MyFragment : Fragment() {
    private lateinit var myViewModel: MyViewModel
    //Other code...

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
      val vm by activityViewModels<MyViewModel> { getViewModelFactory(savedInstanceState) }
      myViewModel = vm
      //Other code...
  }
}
Run Code Online (Sandbox Code Playgroud)