🤖

libs.versions.toml retrofit & viewmodel

2024/12/08に公開

🤔やってみたいこと

  • 🛜Jetpack ComposeでAPI通信をしたい
  • 📡HTTP GETだけでもやってみたく💦
  • Retrofitの使い方

APIからデータを取得する簡単なデモアプリの開発をして知識を学ぶ内容になっております。

こちらのAPIからデータを取得する
https://jsonplaceholder.typicode.com/photos

  • 開発環境
    • M2 Mac
    • Android Studio
    • compileSdk = 35
    • targetSdk = 35

こちらが完成品です参考にしてみてください。

🚀やってみたこと

前回、libs.versions.tomlの記事を書きました。以前だとライブラリの追加は、build.gradle.ktsにこのように追加していました。

implementation 'com.squareup.retrofit2:retrofit:(insert latest version)'

今だとdouble quoteと()で囲んでいますけどね。こんな感じで。

implementation ("com.squareup.retrofit2:retrofit:(insert latest version)")

今回使用したライブラリは、API通信に必要なRetrofitと状態管理をするviewmodelです。こちらを追加しておいてください。

https://square.github.io/retrofit/
https://developer.android.com/jetpack/androidx/releases/lifecycle?hl=ja

build.gradle.ktsの設定
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.kotlin.compose)
}

android {
    namespace = "com.example.jetpackhttpdemo"
    compileSdk = 35

    defaultConfig {
        applicationId = "com.example.jetpackhttpdemo"
        minSdk = 24
        targetSdk = 35
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = "11"
    }
    buildFeatures {
        compose = true
    }
}

dependencies {

    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.activity.compose)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.ui)
    implementation(libs.androidx.ui.graphics)
    implementation(libs.androidx.ui.tooling.preview)
    implementation(libs.androidx.material3)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
    androidTestImplementation(platform(libs.androidx.compose.bom))
    androidTestImplementation(libs.androidx.ui.test.junit4)
    debugImplementation(libs.androidx.ui.tooling)
    debugImplementation(libs.androidx.ui.test.manifest)
    // この下にライブラリを追加して補完機能で設定を変更する。
    // Retrofit for API requests
    implementation (libs.retrofit)
    implementation (libs.converter.gson)
    // ViewModel and LiveData for MVVM architecture
    implementation (libs.androidx.lifecycle.viewmodel.compose)
    implementation (libs.androidx.lifecycle.livedata)
}

Permissionの許可をする。

data/ディレクトリを作成してdata classを作成する。APIのJSONのデータ構造に合わせて作成。

package com.example.jetpackhttpdemo.data

data class Post(
    val id: Int,
    val userId: Int,
    val title: String,
    val body: String
)

api/ディレクトリにinterfaceを作成します。postsというエンドポイントへアクセスする設定をします。

package com.example.jetpackhttpdemo.api

import com.example.jetpackhttpdemo.data.Post
import retrofit2.http.GET

interface JsonPlaceholderApi {
    @GET("posts")
    suspend fun getPosts(): List<Post>
}

repository/ディレクトリを作成する。こちらでbaseUrlを設定します。先ほどのinterfacegetPosts()メソッドを呼び出してHTTP GETするメソッドを呼び出しています。

package com.example.jetpackhttpdemo.repository

import com.example.jetpackhttpdemo.api.JsonPlaceholderApi
import com.example.jetpackhttpdemo.data.Post
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class PostRepository {
    private val api: JsonPlaceholderApi by lazy {
        Retrofit.Builder()
            .baseUrl("https://jsonplaceholder.typicode.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(JsonPlaceholderApi::class.java)
    }

    suspend fun getPosts(): List<Post> = api.getPosts()
}

viewmodel/ディレクトリを作成してView側にAPIから取得したデーターを渡す状態管理をするためのViewModelを作成します。

package com.example.jetpackhttpdemo.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.jetpackhttpdemo.data.Post
import com.example.jetpackhttpdemo.repository.PostRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class PostViewModel : ViewModel() {
    private val repository = PostRepository()
    private val _posts = MutableStateFlow<List<Post>>(emptyList())
    val posts: StateFlow<List<Post>> = _posts

    private val _isLoading = MutableStateFlow(false)
    val isLoading: StateFlow<Boolean> = _isLoading

    private val _error = MutableStateFlow<String?>(null)
    val error: StateFlow<String?> = _error

    init {
        loadPosts()
    }

    fun loadPosts() {
        viewModelScope.launch {
            try {
                _isLoading.value = true
                _error.value = null
                _posts.value = repository.getPosts()
            } catch (e: Exception) {
                _error.value = e.message
            } finally {
                _isLoading.value = false
            }
        }
    }
}

APIから取得したデータを表示するViewを作成しましょう。

package com.example.jetpackhttpdemo
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.jetpackhttpdemo.data.Post
import com.example.jetpackhttpdemo.viewmodel.PostViewModel

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PostScreen(
    modifier: Modifier = Modifier,
    viewModel: PostViewModel = viewModel()
) {
    val posts by viewModel.posts.collectAsState()
    val isLoading by viewModel.isLoading.collectAsState()
    val error by viewModel.error.collectAsState()

    Column(modifier = modifier) {
        TopAppBar(
            title = { Text("Latest News") },
            colors = TopAppBarDefaults.topAppBarColors(
                containerColor = MaterialTheme.colorScheme.primary,
                titleContentColor = MaterialTheme.colorScheme.onPrimary
            )
        )

        Box(
            modifier = Modifier
                .fillMaxSize()
                .padding(horizontal = 16.dp)
        ) {
            if (isLoading) {
                CircularProgressIndicator(
                    modifier = Modifier.align(Alignment.Center)
                )
            } else if (error != null) {
                Text(
                    text = "Error: $error",
                    color = MaterialTheme.colorScheme.error,
                    modifier = Modifier
                        .align(Alignment.Center)
                        .padding(16.dp)
                )
            } else {
                LazyColumn(
                    modifier = Modifier.fillMaxSize(),
                    contentPadding = PaddingValues(vertical = 16.dp),
                    verticalArrangement = Arrangement.spacedBy(16.dp)
                ) {
                    items(posts) { post ->
                        PostCard(post = post)
                    }
                }
            }
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PostCard(post: Post) {
    Card(
        modifier = Modifier.fillMaxWidth(),
        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
    ) {
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
        ) {
            Text(
                text = post.title,
                style = MaterialTheme.typography.titleMedium,
                fontWeight = FontWeight.Bold
            )
            Spacer(modifier = Modifier.height(8.dp))
            Text(
                text = post.body,
                style = MaterialTheme.typography.bodyMedium
            )
        }
    }
}

最後ににMainActivity.ktでPostScreenを読み込めばデモアプリの完成です。

package com.example.jetpackhttpdemo

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.ui.Modifier
import com.example.jetpackhttpdemo.ui.theme.JetpackHttpDemoTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            JetpackHttpDemoTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    PostScreen(modifier = Modifier.fillMaxSize().padding(innerPadding))
                }
            }
        }
    }
}

ビルドに成功すれば期待する動作になっているはず

🙂最後に

今回は、libs.versions.toml retrofit & viewmodelのタイトルの通り新しいライブラリの追加方法でRetrofitとViewModelを使用する方法について解説いたしました。

以前とやり方が変わっているので焦りました(^_^;)

Discussion