0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Preferences DataStore(旧SharedPreferences)の使い方あれこれ

Posted at

初めに

DataStoreCoroutineFlowを使ってデータにアクセスでき、JetpackComposeとも親和性が高いため、SharedPreferencesを使っている場合は移行を推奨されています。コードラボ に基本的な使い方の説明がありますが、できる限りDataStore以外の知識を必要としない書き方になっていて、余りスマートな使い方ではありません。また、取得箇所や方法も色々あるので、いくつか紹介したいと思います。

DataStoreの設定

DataStoreのインスタンスと状態を保持するAppSettingsStateデータクラス、PreferencesからAppSettingsStateへ変換する関数を定義します。

AppSettingsState.kt
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "app_settings")

data class AppSettingsState(
    val count: Int
)

private val COUNT = intPreferencesKey("count")

fun Preferences.mapAppSettingsState(): AppSettingsState {
    return AppSettingsState(
        count = this[COUNT] ?: 0
    )
}

Activityで監視

onCreate内でlifecycleScopeを使ってCoroutineを起動、内部でDataStoreを監視し、AppSettingsStateを取得、Composableへ引き渡す方法です。
Activityだけでなく、LifecycleServiceを継承したServiceでも同じ使い方ができます。

MainActivity.kt
private var appSettings by mutableStateOf(AppSettingsState(0))

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    lifecycleScope.launch {
        lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
            dataStore.data.onEach {
                appSettings = it.mapAppSettingsState()
            }.collect()
        }
    }

    setContent {
        DataStoreSampleTheme {
            Surface(
                modifier = Modifier.fillMaxSize(),
                color = MaterialTheme.colorScheme.background
            ) {
                MainPage(appSettings)
            }
        }
    }
}
MainPage.kt
@Composable
fun MainPage(
    appSettings: AppSettingsState
) {
    // TODO
}

ViewModel経由

ViweModelDataStoreを参照する方法は色々ありますが、今回はHiltを使ってDIする方法を取ります。また、直接DataStoreを参照するのではなく、リポジトリ層を経由して参照するようにします。

Hiltの導入方法については下記を参照してください。
Hilt を使用した依存関係挿入
hiltViewModel()を使用するので、上記の手順に加えてモジュールの追加が必要です。

implementation("androidx.hilt:hilt-navigation-compose:1.2.0")

Hiltが参照できるよう、モジュールへの登録を行います。他にもDataStore<Preferences>型のモジュールを使用する場合もあるので、別名を使って指定できるようにしています。

AppSettingsState.kt
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AppSettingsDataStore

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
    companion object {
        @AppSettingsDataStore
        @Provides
        @Singleton
        fun provideAppSettingsDataStore(
            @ApplicationContext applicationContext: Context
        ): DataStore<Preferences> {
            return applicationContext.dataStore
        }
    }
}

リポジトリでは、コストラクタインジェクションでDataStoreを取得し、Flow<AppSettingsState>に変換します。

AppSettingsRepository.kt
interface AppSettingsRepository {
    val appSettingsFlow: Flow<AppSettingsState>
}

class AppSettingsRepositoryImpl 
    @Inject constructor(@AppSettingsDataStore private val dataStore: DataStore<Preferences>)
    : AppSettingsRepository {
    
    override val appSettingsFlow =
        dataStore.data.map {
            it.mapAppSettingsState()
        }
}

Hiltが参照できるよう、モジュールへの登録を行います。

AppSettingsState.kt
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
    companion object {

        // ...
    
        @Binds
        @Singleton
        abstract fun bindAppSettingsRepository(
            appSettingsRepository: AppSettingsRepositoryImpl
        ): AppSettingsRepository
    }
}

ViewModelでは、コストラクタインジェクションでリポジトリを取得し、StateFlow<AppSettingsState>に変換します。

MainPageViewModel.kt
@HiltViewModel
class MainPageViewModel 
    @Inject constructor(private val appSettingsRepository: AppSettingsRepository)
    : ViewModel() {
    
    private val _appSettingsState =
        appSettingsRepository.appSettingsFlow.stateIn(viewModelScope, SharingStarted.Eagerly, AppSettingsState(0))

    val appSettingsState: StateFlow<AppSettingsState>
        get() {
            return _appSettingsState
        }
}

ComposableではViewModel経由で取得することができます。

MainPage.kt
@Composable
fun MainPage(
    mainPageViewModel: MainPageViewModel = hiltViewModel(),
) {
    val appSettings by mainPageViewModel.appSettingsState.collectAsState()

    // TODO
}

ViewModelを使わない方法

Jetpack ComposeでViewModelを使わずに、Composable関数を使って状態とロジックを切り出す! でも書かれているように、JetpackComposeではViewModelは不要で、状態を返すComposable関数を使いましょうという意見もあります。下記はその例です。

State<AppSettingsState>を返すComposable関数を作成し、Composableから呼び出して取得します。

AppSettingsState.kt
@Composable
fun rememberAppSettingsState(): State<AppSettingsState> {
    var appSettings by remember { mutableStateOf(AppSettingsState(0)) }
    val context  = LocalContext.current
    val composableScope = rememberCoroutineScope()

    LaunchedEffect(Unit) {
        context.dataStore.data.onEach {
            appSettings = it.mapAppSettingsState()
        }.stateIn(composableScope)
    }

    return remember (appSettings) {
        mutableStateOf(appSettings)
    }
}
MainPage.kt
@Composable
fun MainPage() {
    val appSettings by rememberAppSettingsState()

    // TODO
}

終わりに

DataStoreの変更された値を、Composableで参照する方法をいくつか紹介してみました。Roomを使ったデータの永続化の場合も、Flowを返すDaoを定義できるので、同じような使い方ができます。それぞれのプロジェクトに合った使い方の参考になれば幸いです。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?