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

时间:2017-12-04 07:43:58

标签: android mvvm android-databinding

我目前正在使用databindingMVVM architecture来安装Android。在ViewModel中获取字符串资源的最佳方法是什么。

我没有使用新的AndroidViewModel组件,eventbusRxJava

我正在经历接口的方法,其中Activity将负责提供资源。但最近我在this回答中发现了一个类似的问题,其中使用应用程序上下文的单个类提供了所有资源。

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

8 个答案:

答案 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来完成这项工作。

ViewModel

 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)