![](/img/trans.png)
[英]How to Get R.string in ViewModel Class of DataBinding in Android
[英]How should I get Resources(R.string) in viewModel in Android (MVVM and databinding)
我目前正在为 android 使用
databinding<\/code>和
MVVM architecture<\/code> 。
在 ViewModel 中获取字符串资源的最佳方法是什么。
我没有使用新的
AndroidViewModel<\/code>组件、事件
RxJava<\/code>或
eventbus<\/code><\/strong>
我正在经历 Activity 将负责提供资源的接口方法。 但最近我发现了一个与此<\/a>答案类似的问题,其中使用应用程序上下文的单个类提供所有资源。
哪种方法更好? 或者还有什么我可以尝试的吗?
您可以通过实现 AndroidViewModel 而不是 ViewModel 来访问上下文。
class MainViewModel(application: Application) : AndroidViewModel(application) {
fun getSomeString(): String? {
return getApplication<Application>().resources.getString(R.string.some_string)
}
}
您还可以使用 Resource Id 和 ObservableInt 来完成这项工作。
视图模型:
val contentString = ObservableInt()
contentString.set(R.string.YOUR_STRING)
然后你的视图可以得到这样的文本:
android:text="@{viewModel.contentString}"
通过这种方式,您可以将上下文排除在 ViewModel 之外
只需创建一个使用应用程序上下文获取资源的 ResourceProvider 类。 在您的 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;
}
资源提供者
public class ResourcesProvider {
private Context mContext;
public ResourcesProvider(Context context){
mContext = context;
}
public String getString(){
return mContext.getString(R.string.some_string);
}
视图模型
public class MyViewModel extends ViewModel {
private ResourcesProvider mResourcesProvider;
public MyViewModel(ResourcesProvider resourcesProvider){
mResourcesProvider = resourcesProvider;
}
public String doSomething (){
return mResourcesProvider.getString();
}
视图模型工厂
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");
}
}
您可以使用资源 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()
}
}
}
使用 Hilt 的 Bozbi 答案的更新版本
视图模型.kt
@HiltViewModel
class MyViewModel @Inject constructor(
private val resourcesProvider: ResourcesProvider
) : ViewModel() {
...
fun foo() {
val helloWorld: String = resourcesProvider.getString(R.string.hello_world)
}
...
}
资源提供者.kt
@Singleton
class ResourcesProvider @Inject constructor(
@ApplicationContext private val context: Context
) {
fun getString(@StringRes stringResId: Int): String {
return context.getString(stringResId)
}
}
理想情况下,应该使用数据绑定,通过解析 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 内部使用的Object...objects
,而不是会导致崩溃的Objects[] objects
。
我不使用数据绑定,但我想您可以为我的解决方案添加一个适配器。
我在视图模型中保留资源 ID
class ExampleViewModel: ViewModel(){
val text = MutableLiveData<NativeText>(NativeText.Resource(R.String.example_hi))
}
并在视图层上获取文本。
viewModel.text.observe(this) { text
textView.text = text.toCharSequence(this)
}
您可以在文章中阅读有关原生文本的更多信息
资源字符串操作属于 View 层,而不是 ViewModel 层。
ViewModel 层应该不依赖于Context
和资源。 定义 ViewModel 将发出的数据类型(类或枚举)。 DataBinding 可以访问 Context 和资源,并且可以在那里解决它。 通过@BindingAdapter
(如果你想要干净的外观)或一个简单的静态方法(如果你想要灵活性和详细性),它接受枚举和Context
并返回String
: android:text="@{MyStaticConverter.someEnumToString(viewModel.someEnum, context)}"
。 ( context
是每个绑定表达式中的合成参数)
但在大多数情况下, String.format
足以将资源字符串格式与 ViewModel 提供的数据结合起来。
看起来“在 XML 中太多了”,但 XML 和绑定是视图层。 如果您丢弃上帝对象,则视图逻辑的唯一位置:活动和片段。
//编辑 - 更详细的例子(kotlin):
object MyStaticConverter {
@JvmStatic
fun someEnumToString(type: MyEnum?, context: Context): String? {
return when (type) {
null -> null
MyEnum.EENY -> context.getString(R.string.some_label_eeny)
MyEnum.MEENY -> context.getString(R.string.some_label_meeny)
MyEnum.MINY -> context.getString(R.string.some_label_miny)
MyEnum.MOE -> context.getString(R.string.some_label_moe)
}
}
}
XML 中的用法:
<data>
<import type="com.example.MyStaticConverter" />
</data>
...
<TextView
android:text="@{MyStaticConverter.someEnumToString(viewModel.someEnum, context)}".
对于更复杂的情况(例如将资源标签与来自 API 的文本混合),请使用密封类而不是枚举,该类将携带来自 ViewModel 的动态String
到将进行组合的转换器。
“转换器”(一组无关的、静态的和无状态的函数)是我经常使用的一种模式。 它允许让所有 Android 的View
相关类型远离 ViewModel 并在整个应用程序中重复使用小的重复部分(例如将 bool 或各种状态转换为 VISIBILITY 或格式化数字、日期、距离、百分比等)。 这消除了许多重叠@BindingAdapter
的需要,恕我直言,增加了 XML 代码的可读性。
对于您不想重构的旧代码,您可以创建一个临时类
private typealias ResCompat = AppCompatResources
@Singleton
class ResourcesDelegate @Inject constructor(
@ApplicationContext private val context: Context,
) {
private val i18nContext: Context
get() = LocaleSetter.createContextAndSetDefaultLocale(context)
fun string(@StringRes resId: Int): String = i18nContext.getString(resId)
fun drawable(@DrawableRes resId: Int): Drawable? = ResCompat.getDrawable(i18nContext, resId)
}
然后在您的AndroidViewModel
使用它。
@HiltViewModel
class MyViewModel @Inject constructor(
private val resourcesDelegate: ResourcesDelegate
) : AndroidViewModel() {
fun foo() {
val helloWorld: String = resourcesDelegate.string(R.string.hello_world)
}
如果您使用的是 Dagger Hilt,那么 @ApplicationContext context: Context 在您的 viewModel 构造函数中将起作用。 Hilt 可以使用此注释自动注入应用程序上下文。 如果您使用的是 dagger,那么您应该通过模块类提供上下文,然后注入 viewModel 构造函数。 最后使用该上下文您可以访问字符串资源。 像 context.getString(R.strings.name)
对我来说最快最简单的方法是使用 AndroidViewModel 而不是 ViewModel:
在您的 ViewModel (Kotlin) 中
val resources = getApplication<Application>().resources
// Then access it with
resources.getString(R.string.myString)
在您的 ViewModel (Java)
getApplication().getResources().getString(status)
创建从 Application 扩展的 MyApplication 类,您可以在每个活动和类中使用。
MyApplication.getContext().getResources().getString(R.string.blabla);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.