我应该如何在Android中的viewModel中获取资源(R.string)(MVVM和数据绑定)

Shu*_*ham 18 android mvvm android-databinding

我目前正在使用databindingMVVM architectureAndroid.在ViewModel中获取字符串资源的最佳方法是什么.

我没有使用新AndroidViewModel组件,eventbusRxJava

我正在经历接触的方式,其中Activity将负责提供资源.但最近我在这个答案中找到了一个类似的问题,其中使用应用程序上下文的单个类提供了所有资源.

哪种方法更好?或者还有其他我可以尝试的东西吗?

Age*_*t_L 29

一点也不。

资源字符串操作属于 View 层,而不是 ViewModel 层。

ViewModel 层应该不依赖于Context资源。定义 ViewModel 将发出的数据类型(类或枚举)。DataBinding 可以访问 Context 和资源,并可以在那里解析它。您所需要的只是一个简单的静态方法,它接受枚举和Context并返回String

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

(文件名为MyStaticConverter.kt,因此在 Java 和 XML 内联 Java 中,它被称为“MyStaticConverterKt”)。

XML 中的用法 -context是一个合成参数,可在每个绑定表达式中使用:

<data>
    <import type="com.example.MyStaticConverterKt" />
</data>
...
<TextView
    android:text="@{MyStaticConverterKt.someEnumToString(viewModel.someEnum, context)}".
Run Code Online (Sandbox Code Playgroud)

这可能看起来像“XML 中的代码太多”,但 XML 和绑定是视图层。视图逻辑的唯一位置 - 如果您拒绝上帝对象:活动和片段。

在大多数情况下,String.format将资源字符串格式与 ViewModel 发出的其他数据结合起来就足够了。对于更复杂的情况(例如将资源标签与 API 中的文本混合),请使用密封类而不是枚举,该密封类会将动态String从 ViewModel 传递到将进行组合的转换器。

对于最简单的情况,例如问题,根本不需要Context显式调用。内置适配器已经将 int 到 text 的绑定解释为字符串资源 id。微小的不便之处在于,当使用null转换器调用时仍然必须返回有效的 ID,因此您需要定义某种占位符,例如<string name="empty" translatable="false"/>.

@StringRes
fun someEnumToString(type: MyEnum?): Int {
    return when (type) {
        MyEnum.EENY -> R.string.some_label_eeny
        MyEnum.MEENY -> R.string.some_label_meeny
        MyEnum.MINY -> R.string.some_label_miny
        MyEnum.MOE -> R.string.some_label_moe
        null -> R.string.empty
    }
}
Run Code Online (Sandbox Code Playgroud)

是的,从技术上讲,您可以直接从 ViewModel 发出 a @StringRes Int,但这会使您的 ViewModel 依赖于资源,因此我强烈建议不要这样做。

“转换器”(不相关的静态和无状态函数的集合)是我经常使用的模式。它允许使所有 AndroidView相关类型远离 ViewModel,并在整个应用程序中重用小型重复部分(例如将 bool 或各种状态转换为 VISIBILITY 或格式化数字、日期、距离、百分比等)。这消除了许多重叠的需要,@BindingAdapter恕我直言,增加了 XML 内联 Java 的可读性。


Exp*_* be 23

您可以通过实现AndroidViewModel而不是ViewModel来访问上下文.

class MainViewModel(application: Application) : AndroidViewModel(application) {
    fun getSomeString(): String? {
        return getApplication<Application>().resources.getString(R.string.some_string)
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这不会在配置更改(例如语言环境更改)上造成错误。既然应用程序的资源不知道这些配置更改? (7认同)
  • 实际上,Google开发人员刚刚发布了一篇有关访问ViewModel中资源的文章。https://medium.com/androiddevelopers/locale-changes-and-the-androidviewmodel-antipattern-84eb677660d9 (4认同)
  • 不要这样做!@11mo 你是对的,当用户更改设备语言时,它会产生错误,但 ViewModel 将引用过时的语言资源。 (4认同)
  • 优先选择 **ViewModel** 而不是 **AndroidViewModel** 以避免资源泄漏。 (3认同)

Ana*_*K R 17

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

资源提供者.kt

@Singleton
class ResourcesProvider @Inject constructor(
    @ApplicationContext private val context: Context
) {
    fun getString(@StringRes stringResId: Int): String {
        return context.getString(stringResId)
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 如果用户更改应用程序的语言设置,这种方法是否不会根据之前的用户语言选择返回字符串值?例如,如果我使用首选语言英语操作我的应用程序,后来决定将语言首选项更改为西班牙语,则 ResourceProvider 仍会返回英语字符串文字。 (10认同)

小智 9

只需创建一个使用应用程序上下文获取资源的 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;
}
Run Code Online (Sandbox Code Playgroud)

资源提供者

public class ResourcesProvider {
private Context mContext;

public ResourcesProvider(Context context){
    mContext = context;
}

public String getString(){
    return mContext.getString(R.string.some_string);
}
Run Code Online (Sandbox Code Playgroud)

视图模型

public class MyViewModel extends ViewModel {

private ResourcesProvider mResourcesProvider;

public MyViewModel(ResourcesProvider resourcesProvider){
    mResourcesProvider = resourcesProvider; 
}

public String doSomething (){
    return mResourcesProvider.getString();
}
Run Code Online (Sandbox Code Playgroud)

视图模型工厂

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

}


ali*_*ter 6

您也可以使用Resource Id和ObservableInt来完成这项工作。

ViewModel

val contentString = ObservableInt()

contentString.set(R.string.YOUR_STRING)
Run Code Online (Sandbox Code Playgroud)

然后,您的视图可以获取如下文本:

android:text="@{viewModel.contentString}"
Run Code Online (Sandbox Code Playgroud)

这样,您可以将上下文保留在ViewModel中

  • 这需要数据绑定。远离它,因为 XML 中存在噪音。 (5认同)
  • 如果字符串有一些参数怎么办? (2认同)

小智 6

您可以使用资源 ID 来完成此操作。

视图模型

 val messageLiveData= MutableLiveData<Any>()

messageLiveData.value = "your text ..."
Run Code Online (Sandbox Code Playgroud)

或者

messageLiveData.value = R.string.text
Run Code Online (Sandbox Code Playgroud)

然后在片段或活动中使用它,如下所示:

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