簡體   English   中英

"我應該如何在 Android 的 viewModel 中獲取 Resources(R.string)(MVVM 和數據綁定)"

[英]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並返回Stringandroid: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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM