Введение

В моем приложении для Android я использовал SharedPreferences для хранения простых строк для функциональности. Недавно я узнал, что настройки по умолчанию устарели. Так что я пошел и искал альтернативы.

Довольно быстрый поиск в Google показал в качестве альтернативы JetPack DataStore-Library. Я знал, что существует возможность реализации базы данных SQLite, но мне хотелось иметь другой облегченный вариант, такой как SharedPreferences: небольшой объем кода, но высокая применимость.

Мне удалось найти множество руководств по Kotlin, но я программировал на Java и хотел продолжать это делать. С инструкциями на сайте я мог начать думать о том, как это может работать. Но процедура оказалась не такой простой, как я думал.

Тем не менее, поскольку я не нашел хорошего руководства о том, как это сделать, но нашел решение, позволяющее заставить его работать очень похоже на SharedPreferences, я хочу поделиться процедурой.

инструкции

Я работаю с Android Studio IDE в версии Flamingo | 2022.2.1. Также хочу отметить, что Java-версия 11 и я использую лямбда-выражения.

Чтобы использовать DataStore-Library, я добавил следующие зависимости в файл app-gradle:

//DataStore instead of SharedPreferences
implementation "androidx.datastore:datastore-preferences:1.0.0"
//RxJava2 support
implementation "androidx.datastore:datastore-preferences-rxjava2:1.0.0"

После быстрой синхронизации библиотеки должны быть доступны для использования.

В верхней части упражнения я инициализировал переменную dataStoreRX типа RxDataStore для запуска, а затем в методе onCreate DataStore можно построить с помощью DataStoreBuilder. :

RxDataStore<Preferences> dataStoreRX;

protected void onCreate(Bundle savedInstanceState) {
  ...
  dataStoreRX = new RxPreferenceDataStoreBuilder(this, "name_for_datastore").build();
}

На этом я надеялся практически закончить, DataStore построен, так что я могу начать сохранять и извлекать значения. Я думал, что это будет конец, в любом случае, если вы захотите использовать DataStore только в одном действии, это будет конец.

Однако, если вы хотите, чтобы DataStore сохранялся через несколько действий, вы должны принять во внимание, что может присутствовать только один экземпляр:

Никогда не создавайте более одного экземпляра DataStore для данного файла в одном процессе.

Но я не хотел помещать DataStore в Bundle и всегда отправлять его следующей активности с помощью putExtra. Поскольку я хотел иметь возможность переключаться между действиями, не переписывая код для всего этого, я искал другую возможность.

Я нашел Синглтон, с помощью которого можно сохранить что угодно как отдельный экземпляр и использовать его не только в одном действии. Это в значительной степени предпосылка DataStore.

Итак, я создал новый java-класс DataStoreSingleton, конкретное имя выбрать не важно, просто оно должно быть таким же в самом классе:

public class DataStoreSingleton {
   RxDataStore<Preferences> datastore;
   private static final DataStoreSingleton ourInstance = new DataStoreSingleton();
   public static DataStoreSingleton getInstance() {
      return ourInstance;
   }
   private DataStoreSingleton() { }
   public void setDataStore(RxDataStore<Preferences> datastore) {
      this.datastore = datastore;
   }
   public RxDataStore<Preferences> getDataStore() {
      return datastore;
   }
}

С помощью двух функций setDataStore и getDataStore можно использовать Singleton. В методе onCreate я реализовал это так:

DataStoreSingleton dataStoreSingleton = DataStoreSingleton.getInstance();
if (dataStoreSingleton.getDataStore() == null) {
    dataStoreRX = new RxPreferenceDataStoreBuilder(this, TAG_STORE_NAME).build();
} else {
    dataStoreRX = dataStoreSingleton.getDataStore();
}
dataStoreSingleton.setDataStore(dataStoreRX);

Если DataStore еще не построен (getDataStore возвращает null), он создается с помощью упомянутого DataStoreBuilder, в противном случае он назначается переменной. В любом случае для этого и следующего действия устанавливается Singleton for DataStore.

Чтобы сохранить значение, я создал метод putStringValue, чтобы он выглядел и работал как функция SharedPreferences:

public boolean putStringValue(String Key, String value){
    boolean returnvalue;
    Preferences.Key<String> PREF_KEY = PreferencesKeys.stringKey(Key);
    Single<Preferences> updateResult =  dataStoreRX.updateDataAsync(prefsIn -> {
      MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
      mutablePreferences.set(PREF_KEY, value);
      return Single.just(mutablePreferences);
    }).onErrorReturnItem(pref_error);
    returnvalue = updateResult.blockingGet() != pref_error;
    return returnvalue;
}

То же самое с получением значения с помощью функции getStringValue:

String getStringValue(String Key) {
    Preferences.Key<String> PREF_KEY = PreferencesKeys.stringKey(Key);
    Single<String> value = dataStoreRX.data().firstOrError().map(prefs -> prefs.get(PREF_KEY)).onErrorReturnItem("null");
    return value.blockingGet();
}

Довольно небольшой объем кода, но мне потребовалось относительно много времени, чтобы понять, что blockingGet можно использовать для извлечения значения из Single.

DataStore не принимает обычные строки в качестве ключей, поэтому мы должны создать Preference.Keys типа String. Соответствующее значение также должно быть строкой (ну, конечно, нет? :)). Но это также означает, что для хранения другого типа мы должны добавить разные методы (putBooleanValue с целым числом в качестве ключа, например). . Чтобы поймать ошибку при сохранении или извлечении значений, я добавил onErrorReturnItem, который делает именно то, на что он похож: он возвращает указанный элемент, если возникает ошибка. Для сохранения это должна быть переменная Preferences, но поскольку мы извлекаем строку, это может быть переменная String. Pref_error не обязательно должен быть таким особенным, он используется только для проверки того, ЕСЛИ он возвращается, то возвращается соответствующее значение. Строка — это буквально просто «ошибка», которая возвращается.

Эти два метода сохраняют и извлекают значения почти так же, как и с SharedPreferences.

Опираясь на это

Чтобы было еще проще, я снова создал новый java-класс: DataStoreHelper

public class DataStoreHelper {
    Activity activity;
    RxDataStore<Preferences> dataStoreRX;
    Preferences pref_error = new Preferences() {...};
    
    public DataStoreHelper(Activity activity, RxDataStore<Preferences> dataStoreRX) {
        this.activity = activity;
        this.dataStoreRX = dataStoreRX;
    }

    public boolean putStringValue(String Key, String value){...}
    String getStringValue(String Key) {...}

    public boolean putBoolValue(String Key, boolean value){...}
    boolean getBoolValue(String Key) {...}
}

В этом классе я могу один раз перечислить все необходимые переменные и методы и вызвать их из любого действия с помощью:

DataStoreHelper dataStoreHelper;
dataStoreHelper = new DataStoreHelper(this, dataStoreRX);

dataStoreHelper.putStringValue(TAG_SEND, TAG_ONLINE);
dataStoreHelper.getStringValue(TAG_SEND);

Я надеюсь, что этот урок поможет вам, если вы окажетесь в ситуации, в которой оказался я! Удачного кодирования, и не стесняйтесь спрашивать о чем угодно.