我目前正在使用databinding
和MVVM architecture
来安装Android。在ViewModel中获取字符串资源的最佳方法是什么。
我没有使用新的AndroidViewModel
组件,eventbus
或RxJava
我正在经历接口的方法,其中Activity将负责提供资源。但最近我在this回答中发现了一个类似的问题,其中使用应用程序上下文的单个类提供了所有资源。
哪种方法更好?或者还有其他我可以尝试的东西吗?
答案 0 :(得分:19)
您可以通过实现AndroidViewModel而不是ViewModel来访问上下文。
class MainViewModel(application: Application) : AndroidViewModel(application) {
fun getSomeString(): String? {
return getApplication<Application>().resources.getString(R.string.some_string)
}
}
答案 1 :(得分:5)
只需创建一个ResourceProvider类即可使用Application上下文获取资源。在ViewModelFactory中,使用App上下文实例化资源提供程序。您的Viewmodel是无上下文的,并且可以通过模拟ResourceProvider轻松进行测试。
应用
public class App extends Application {
private static Application sApplication;
@Override
public void onCreate() {
super.onCreate();
sApplication = this;
}
public static Application getApplication() {
return sApplication;
}
ResourcesProvider
public class ResourcesProvider {
private Context mContext;
public ResourcesProvider(Context context){
mContext = context;
}
public String getString(){
return mContext.getString(R.string.some_string);
}
ViewModel
public class MyViewModel extends ViewModel {
private ResourcesProvider mResourcesProvider;
public MyViewModel(ResourcesProvider resourcesProvider){
mResourcesProvider = resourcesProvider;
}
public String doSomething (){
return mResourcesProvider.getString();
}
ViewModelFactory
public class ViewModelFactory implements ViewModelProvider.Factory {
private static ViewModelFactory sFactory;
private ViewModelFactory() {
}
public static ViewModelFactory getInstance() {
if (sFactory == null) {
synchronized (ViewModelFactory.class) {
if (sFactory == null) {
sFactory = new ViewModelFactory();
}
}
}
return sFactory;
}
@SuppressWarnings("unchecked")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (modelClass.isAssignableFrom(MainActivityViewModel.class)) {
return (T) new MainActivityViewModel(
new ResourcesProvider(App.getApplication())
);
}
throw new IllegalArgumentException("Unknown ViewModel class");
}
}
答案 2 :(得分:4)
您还可以使用资源ID和ObservableInt来完成这项工作。
ViewModel :
val contentString = ObservableInt()
contentString.set(R.string.YOUR_STRING)
然后您的视图可以获取如下文本:
android:text="@{viewModel.contentString}"
这样,您可以将上下文保留在ViewModel中
答案 3 :(得分:2)
使用 Hilt 的 Bozbi 答案的更新版本
ViewModel.kt
@HiltViewModel
class MyViewModel @Inject constructor(
private val resourcesProvider: ResourcesProvider
) : ViewModel() {
...
fun foo() {
val helloWorld: String = resourcesProvider.getString(R.string.hello_world)
}
...
}
ResourcesProvider.kt
@Singleton
class ResourcesProvider @Inject constructor(
@ApplicationContext private val context: Context
) {
fun getString(@StringRes stringResId: Int): String {
return context.getString(stringResId)
}
}
答案 4 :(得分:1)
您可以使用资源ID来完成这项工作。
val messageLiveData= MutableLiveData<Any>()
messageLiveData.value = "your text ..."
或
messageLiveData.value = R.string.text
然后将其用于片段或活动中,如下所示:
messageLiveData.observe(this, Observer {
when (it) {
is Int -> {
Toast.makeText(context, getString(it), Toast.LENGTH_LONG).show()
}
is String -> {
Toast.makeText(context, it, Toast.LENGTH_LONG).show()
}
}
}
答案 5 :(得分:1)
理想情况下应该使用数据绑定,通过解析 xml 文件中的字符串可以轻松解决此问题。但是在现有项目中实现数据绑定可能太多了。
对于这样的情况,我创建了以下类。它涵盖了带或不带参数的所有字符串情况,并且不需要 viewModel 扩展 AndroidViewModel,这种方式也涵盖了 Locale 更改的事件。
class ViewModelString private constructor(private val string: String?,
@StringRes private val stringResId: Int = 0,
private val args: ArrayList<Any>?){
//simple string constructor
constructor(string: String): this(string, 0, null)
//convenience constructor for most common cases with one string or int var arg
constructor(@StringRes stringResId: Int, stringVar: String): this(null, stringResId, arrayListOf(stringVar))
constructor(@StringRes stringResId: Int, intVar: Int): this(null, stringResId, arrayListOf(intVar))
//constructor for multiple var args
constructor(@StringRes stringResId: Int, args: ArrayList<Any>): this(null, stringResId, args)
fun resolve(context: Context): String {
return when {
string != null -> string
args != null -> return context.getString(stringResId, *args.toArray())
else -> context.getString(stringResId)
}
}
}
例如我们有这个带有两个参数的资源字符串
<string name="resource_with_args">value 1: %d and value 2: %s </string>
在 ViewModel 类中:
myViewModelString.value = ViewModelString(R.string.resource_with_args, arrayListOf(val1, val2))
在 Fragment 类中(或任何有可用上下文的地方)
textView.text = viewModel.myViewModelString.value?.resolve(context)
请记住,*
上的 *args.toArray()
不是打字错误,因此不要将其删除。将数组表示为 Object...objects
的语法是 Android 内部使用的,而不是会导致崩溃的 Objects[] objects
。
答案 6 :(得分:-2)
创建从Application扩展的MyApplication类,您可以在每个Activity和类中使用。
MyApplication.getContext().getResources().getString(R.string.blabla);
答案 7 :(得分:-3)
对我而言,最快,最简单的方法是使用:
在ViewModel(科特林)中
val resources = getApplication<Application>().resources
// Then access it with
resources.getString(R.string.myString)
在ViewModel(Java)
getApplication().getResources().getString(status)