0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【 Jetpack Compose 】お気に入りの項目だけ表示させる

Posted at

今回実装したもの

お気に入りに登録した観光地のみを表示させる機能を実装しました。

トグルスイッチをオンにするとお気に入りだけが表示されて、オフにすると全ての観光地が表示されます。

実装した機能

今回は、その実装方法をかねてより作成している、観光地リストアプリをベースにして紹介します。

使用するもの

お気に入り機能を実装するために使用したものは以下のものです。

  • LandmarkViewModel.kt
    • 観光地情報を保持する viewModel ですが、今回お気に入りかどうかの情報も保持するようにします
  • FavoriteData.kt
    • お気に入り情報を取得する時のデータの型を指定します
  • Favorite.json
    • お気に入り情報が入った JSON ファイルです
  • FavoriteToggle.kt
    • トグルスイッチを実装します
  • LandmarkList.kt
    • 観光地リストを表示させます
  • MainActivy.kt
    • アプリのライフサイクルを管理します。 viewModel の宣言はここで行います

FavoriteData

ViewModel 取り掛かる前に data class を設定します。

今回は観光地の id とお気に入りか否かの Boolean を読み出します。

FavoriteData.kt
data class FavoriteData(
    val id:Int,
    val isFavorite:Boolean
)

ViewModel

ViewModel では新たにお気に入り情報を取り扱うように変数を追加します。

build.gradle (Module:app) では以下のものを dependencies に追加してください。

build.gradle
    implementation(libs.gson)
    implementation(libs.androidx.runtime.livedata)

そして、 viewModel のコードでは Gson を用いて JSON ファイルから取得したデータをパースして、 LiveData として保持します。

LandmarkViewModel.kt

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 値を返すようになっています。

FavoriteToggle.kt
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 を見てみましょう。

今回はお気に入りだけが表示されるか見るために初期値を埋め込んであります。

ゆくゆくはアプリ側でも操作したいところです。

Favorite.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

お待たせしました!いよいよ画面の実装に入ります。

今回はリストの上にトグルスイッチを配置します。

トグルスイッチはスクロールしても画面の中に留まり続けます。

LandmarkList.kt
// お気に入りスイッチの初期値は 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 に以下のライブラリを追加してください。

build.gradle
implementation(libs.androidx.lifecycle.runtime.ktx)

Activity はこのように実装します。

MainActivity.kt
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)
                }
            }
        }
    }
}

以上で今回の実装は終わりです。

もし、アプリの全体像が見たい方はリポジトリへお越しください。

お疲れ様でした。

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?