今回実装したもの
お気に入りに登録した観光地のみを表示させる機能を実装しました。
トグルスイッチをオンにするとお気に入りだけが表示されて、オフにすると全ての観光地が表示されます。
実装した機能 |
---|
今回は、その実装方法をかねてより作成している、観光地リストアプリをベースにして紹介します。
使用するもの
お気に入り機能を実装するために使用したものは以下のものです。
- LandmarkViewModel.kt
- 観光地情報を保持する viewModel ですが、今回お気に入りかどうかの情報も保持するようにします
- FavoriteData.kt
- お気に入り情報を取得する時のデータの型を指定します
- Favorite.json
- お気に入り情報が入った JSON ファイルです
- FavoriteToggle.kt
- トグルスイッチを実装します
- LandmarkList.kt
- 観光地リストを表示させます
- MainActivy.kt
- アプリのライフサイクルを管理します。 viewModel の宣言はここで行います
FavoriteData
ViewModel 取り掛かる前に data class を設定します。
今回は観光地の id
とお気に入りか否かの Boolean
を読み出します。
data class FavoriteData(
val id:Int,
val isFavorite:Boolean
)
ViewModel
ViewModel では新たにお気に入り情報を取り扱うように変数を追加します。
build.gradle (Module:app)
では以下のものを dependencies
に追加してください。
implementation(libs.gson)
implementation(libs.androidx.runtime.livedata)
そして、 viewModel のコードでは Gson を用いて JSON ファイルから取得したデータをパースして、 LiveData
として保持します。
class LandmarkViewModel(application: Application) : AndroidViewModel(application) {
// landmarks は観光地情報保持 ( 既存 )
private val _landmarks = MutableLiveData<List<LandmarkData>>()
val landmarks: LiveData<List<LandmarkData>> = _landmarks
// favorites はお気に入り情報保持
private val _favorites = MutableLiveData<List<FavoriteData>>()
val favorites: LiveData<List<FavoriteData>> = _favorites
init {
loadLandmarks()
loadFavorites()
}
// loadLandmarks() 観光地情報読み込み
private fun loadFavorites() {
try {
// asset フォルダにアクセスします。
val assetManager = getApplication<Application>().assets
val inputStream = assetManager.open("Favorites.json")
val jsonString = inputStream.bufferedReader().use { it.readText() }
val favorites = object : TypeToken<List<FavoriteData>>() {}.type
_favorites.value = Gson().fromJson(jsonString, favorites)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
FavoriteToggle
トグルスイッチのコンポーネントを作成します。
スイッチが操作されると、その度に Boolean
値を返すようになっています。
fun favoriteToggle(): Boolean {
var isFavorite by remember {
mutableStateOf(false)
}
Switch(
checked = isFavorite,
onCheckedChange = { isChecked -> isFavorite = isChecked },
// スイッチの色はテーマをカラースキームで設定すると本体のテーマカラーに応じて変化します
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colorScheme.primary,
checkedTrackColor =MaterialTheme.colorScheme.primaryContainer,
uncheckedThumbColor = MaterialTheme.colorScheme.secondary,
uncheckedTrackColor = MaterialTheme.colorScheme.secondaryContainer
)
)
return isFavorite
}
Favorite.json
お気に入り情報が入っている JSON を見てみましょう。
今回はお気に入りだけが表示されるか見るために初期値を埋め込んであります。
ゆくゆくはアプリ側でも操作したいところです。
[
// 観光地と同じ個数のデータを入れます。
{"id": 1,"isFavorite": true},
{"id": 2, "isFavorite": false},
{"id": 3, "isFavorite": false},
{"id": 4, "isFavorite": true},
{"id": 5, "isFavorite": false},
{"id": 6,"isFavorite": false},
{"id": 7, "isFavorite": false},
{"id": 8, "isFavorite": true},
{"id": 9, "isFavorite": false},
{"id": 10, "isFavorite": false},
//...
]
LandmarkList
お待たせしました!いよいよ画面の実装に入ります。
今回はリストの上にトグルスイッチを配置します。
トグルスイッチはスクロールしても画面の中に留まり続けます。
// お気に入りスイッチの初期値は OFF
private var isOnFavoriteToggle = false
sealed class Destination(val route: String) {
data object Landmarks : Destination("landmarks")
}
@Composable
fun SetLandmarkView(landmarkViewModel: LandmarkViewModel,navController: NavHostController){
NavHost(navController = navController, startDestination = Destination.Landmarks.route) {
composable(Destination.Landmarks.route) {
LandmarkList(viewModel = landmarkViewModel,navController)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LandmarkList(viewModel: LandmarkViewModel,navController: NavController) {
//viewModel が持っている情報を使用する
val landmarks = viewModel.landmarks.observeAsState(initial = emptyList())
val favoriteDataList = viewModel.favorites.observeAsState(initial = emptyList())
Column {
CenterAlignedTopAppBar(
title = {
Text(
text = stringResource(id = R.string.app_name),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary
)
)
Row(modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically) {
// お気に入りスポットのテキスト
Text(text = stringResource(id = R.string.favoriteTitle), style = MaterialTheme.typography.bodyLarge)
// 中央のスペースを開けるためのスペーサー
Spacer(modifier = Modifier.weight(1f))
// ここでトグルスイッチを設定する
isOnFavoriteToggle = favoriteToggle()
}
LazyColumn {
items(landmarks.value.size) { index ->
// トグルスイッチが OFF もしくは、その観光地がお気に入り登録されていれば表示
if (isOnFavoriteToggle.not() || favoriteDataList.value[index].isFavorite) {
ListItem(landmark = landmarks.value[index], navController = navController)
}
}
item { Spacer(modifier = Modifier.padding(24.dp)) }
}
}
}
@Composable
fun ListItem(landmark: LandmarkData,navController: NavController) {
Column(modifier = Modifier
.fillMaxWidth()
.border(1.dp, Color.Gray)
.padding(16.dp)) {
// 観光地の id や名前などが表示される
Text(text = "No.${landmark.id}:${landmark.name}", style = MaterialTheme.typography.titleLarge)
Text(text = landmark.description,style = MaterialTheme.typography.bodyLarge)
}
}
MainActivy
viewModel は Activity にライフサイクルを管理してもらうために宣言しなければなりません。
忘れがちですが行いましょう。
build.gradle(Module:app) dependencies
に以下のライブラリを追加してください。
implementation(libs.androidx.lifecycle.runtime.ktx)
Activity はこのように実装します。
class MainActivity : ComponentActivity() {
//Android のライブラリである viewModels を使います。
private val landmarkViewModel: LandmarkViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
JetpackListTheme {
Surface {
val navController = rememberNavController()
//リスト画面をセッティングするメソッドに viewModel を継承します。
SetLandmarkView(landmarkViewModel,navController = navController)
}
}
}
}
}
以上で今回の実装は終わりです。
もし、アプリの全体像が見たい方はリポジトリへお越しください。
お疲れ様でした。