kotlinで、外部APIを、repository → viewModel → Activityの流れで呼び出す。
Repositoryの作成
外部APIへのアクセスを担当するrepositoryを作成。repositoryはデータの取得や保存、変換などの責務を持つ。外部APIとの通信を行い、必要なデータを取得する。
class MyRepository {
suspend fun fetchData(): ApiResponse {
// 外部APIからデータを取得する処理を実装
}
}
ViewModelの作成
データの処理とUIの状態管理を担当するviewModelを作成する。viewModelはrepositoryからデータを取得し、必要な加工や変換を行う。また、viewModelはActivityとの間でデータや状態のやり取りを行う。
class MyViewModel(private val repository: MyRepository) : ViewModel() {
private val _data = MutableLiveData<Data>()
val data: LiveData<Data> = _data
fun fetchData() {
viewModelScope.launch {
val response = repository.fetchData()
if (response.isSuccessful) {
val data = response.body() // レスポンスデータを取得
_data.value = data // UIにデータを通知
} else {
// エラーハンドリングなど
}
}
}
}
Activityの作成
ユーザーインターフェースを担当するActivityを作成。ActivityはviewModelと連携し、データや状態の更新を受け取ってUIを更新する。また、ユーザーの操作やイベントをviewModelに伝える。
class MyActivity : AppCompatActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ViewModelの初期化
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
// データの変更を監視し、UIを更新
viewModel.data.observe(this, Observer { data ->
// UIの更新処理
})
// データの取得を開始
viewModel.fetchData()
}
}
repositoryが外部APIとの通信を担当し、viewModelがデータの取得と加工を行い、ActivityがUIの表示とユーザーの操作を扱う。
APIを呼び出すときの、dataクラスについて
APIを呼び出す際には、データの受け取りや送信に使用するデータクラスを定義することが一般的。データクラスは、APIのレスポンスやリクエストのボディに対応するデータ構造を表現する。
データクラスは、Kotlinのdata classキーワードを使用して定義される。
data class User(
val id: String,
val name: String,
val email: String
)
このデータクラスは、id、name、emailというプロパティを持つ。データクラスは自動的に以下の機能を提供する。
- プロパティのゲッターとイコール(
equals())メソッド - プロパティのハッシュコード(
hashCode())メソッド - プロパティの文字列表現(
toString())メソッド - プロパティのコピー(
copy())メソッド
{
"id": "123",
"name": "John",
"email": "john@example.com"
}
このレスポンスデータを受け取るためのデータクラスを定義すると以下のようになる。
data class User(
val id: String,
val name: String,
val email: String
)
そして、APIのレスポンスを受け取る際には、レスポンスデータをデータクラスのインスタンスにマッピングする。
val response = // APIレスポンスを受け取る処理
val user = response.body()?.let { User(it.id, it.name, it.email) }
APIのレスポンスをUserデータクラスのインスタンスに変換。レスポンスのid、name、emailというフィールドをデータクラスのプロパティに対応付ける。
データクラスを使用することで、APIのデータを型安全に扱うことができる。また、データクラスの便利な機能を利用して、比較や表示などの処理を簡潔に記述することができる。
dataクラスで定義する @parcelableはどの様な時に使うのか
@Parcelizeアノテーションは、KotlinのデータクラスをParcelableとして宣言するために使用される。Parcelableは、Androidフレームワークでオブジェクトの状態をシリアライズ(直列化)およびデシリアライズ(非直列化)するためのインターフェース。
-
簡潔な実装
@Parcelizeをデータクラスに適用することで、Parcelableインターフェースの実装が自動的に生成される -
パフォーマンスの向上
ParcelableはJavaのSerializableよりも高速であり、データのシリアライズとデシリアライズにかかるオーバーヘッドが少ないため、パフォーマンスが向上する。
@Parcelizeは、以下のようなケースで使用される。
-
Intentを使用してオブジェクトを他のActivityに渡す必要がある場合。 - データを
Bundleに格納してフラグメント間でやり取りする場合。 -
ViewModelなど、Parcelableが要求されるクラスとの統合が必要な場合。
ただし、@Parcelizeを使用するためには、以下の条件が必要。
- データクラスは直列化可能なプロパティ(基本データ型や他の
Parcelableオブジェクトなど)のみを持つ必要がある。 - データクラスはトップレベルであるか、ネストしたクラスである必要がある。
Parcelableが必要な場合やパフォーマンスの向上が求められる場合には、@Parcelizeを使用すると便利。ただし、シリアライズやデシリアライズの必要がない場合やデータが複雑でない場合には、独自のシリアライゼーション手法を使用することもできる。
@JsonClass(generateAdapter = true)
@Parcelize
data class TestData(
val testItemsCount: TestItemsCount,
val testItemsStamp: TestItemsStamp
) : Parcelable
@JsonClass(generateAdapter = true)
@Parcelize
data class TestItemsCount(
val count_this_month: Int,
val count_total: Int,
val updated_at: String?
) : Parcelable
@JsonClass(generateAdapter = true)
@Parcelize
data class TestItemsStamp(
val title: String,
val detail: String,
val test_flag: Boolean,
val stamps: List<Stamps>
) : Parcelable
@JsonClass(generateAdapter = true)
@Parcelize
data class Stamps(
val number: Int,
val is_stamp: Boolean,
val date: String?
) : Parcelable
実際に叩かれているエンドポイントを確認する
suspend fun fetchTest(id: Int): TestData = withContext(dispatchersProvider.io) {
Log.d("API", "Requesting fetchTest with ID: $id")
val response = testApi.fetchTest(id).await()
Log.d("API", "Response: $response")
response
}
- ログ出力を確認するためには、アプリをビルドして実行し、対象の画面に遷移する必要がある。ログは実行時に出力されるため、実際のアプリの動作中にログを確認することができる。
- アプリを実行する際には、Logcatを使用する。
- アプリが実行されている間は、ログメッセージがリアルタイムに表示されるので、APIリクエストの前後のログやレスポンスデータを確認することができる。
関数の中で、関数を定義する
関数内で定義された関数をローカル関数(Local Function)と呼ぶ。
fun outerFunction() {
println("Outer Function")
fun innerFunction() {
println("Inner Function")
}
innerFunction() // ローカル関数の呼び出し
}
fun main() {
outerFunction()
}
outerFunction内でinnerFunctionというローカル関数を定義。outerFunctionを呼び出すと、"Outer Function"が表示され、その後にinnerFunctionが呼び出され、"Inner Function"が表示される。
ローカル関数は、外部の関数内でのみアクセス可能であり、外部の関数のローカル変数やパラメータにアクセスすることができる。また、ローカル関数は外部から直接アクセスできないため、カプセル化やコードの整理に役立つ。
注意点として、ローカル関数は定義された位置よりも後で使用することはでき無い。つまり、ローカル関数が定義される前に呼び出そうとするとコンパイルエラーになる。
Kotlinでは、ローカル関数は同じスコープ内の他の関数からは直接呼び出すことができ無い。ローカル関数は、定義された関数内でのみアクセス可能。
nullだった場合に、textViewをisVisible = falseにする。
?.letブロックを使用してnull安全な操作を行う。
val data: DataClass? = // データクラスのインスタンスを取得するなど
data?.propertyName?.let { value ->
textView.text = value
textView.isVisible = true
} ?: run {
textView.isVisible = false
}
data?.propertyNameがnullでない場合は、letブロック内の処理が実行される。textView.textに値をセットし、textView.isVisibleをtrueに設定する。
data?.propertyNameがnullの場合は、?.letブロックがスキップされ、runブロックが実行される。runブロック内の処理では、textView.isVisibleをfalseに設定してtextViewを非表示にする。
このようにすることで、nullの場合にtextViewを非表示にすることができる。
onResume内で、現在のviewが非表示かどうかの判定をする
Viewの可視性を直接チェックする方法
onResume内で現在のViewの可視性を直接チェックすることができる。
これには、ViewのisVisibleプロパティを使用する。
override fun onResume() {
super.onResume()
if (view.isVisible) {
// Viewが表示されている場合の処理
} else {
// Viewが非表示の場合の処理
}
}
この方法は、View自体の可視性をチェックするため、親のViewやFragmentの可視性には依存しない
Viewの可視性を親の可視性に基づいてチェックする方法
もし、Viewが他の親のView内に含まれている場合、その親の可視性に基づいて現在のViewの可視性を判定することもできる。これには、ViewのisShownメソッドを使用する。
override fun onResume() {
super.onResume()
if (view.isShown) {
// Viewが表示されている場合の処理
} else {
// Viewが非表示の場合の処理
}
}
この方法は、Viewが他の親のView内に含まれている場合にのみ有効。また、View自体の可視性だけでなく、親の可視性にも依存するため、親の可視性が変更された場合にも適切に反映される。
注意点として、これらの方法はViewの可視性をチェックするだけであり、Viewが非表示の場合の原因(例: setVisibility(View.GONE)による非表示設定)については判定しない。
visibilityと、isVisibleの使い分け
visibilityプロパティ
visibilityプロパティは、Viewの表示状態を設定および取得するためのプロパティ。
visibilityプロパティには、次の3つの値を指定できる。
-
View.VISIBLE: Viewを表示。 -
View.INVISIBLE: Viewを非表示にするが、領域を確保する。 -
View.GONE: Viewを非表示にし、領域も確保しない。
visibilityプロパティは、Int`型であり、設定時には上記の値を指定。直接設定することができ、可視性を細かく制御できる。
isVisibleプロパティ
-
Viewが表示されているかどうかを判定するためのプロパティ。 -
Viewの可視性を基にしてtrueまたはfalseを返す。 -
Boolean型であり、判定のみに使用する。 - 読み取り専用であるため、直接設定することはでき無い。
基本的には、Viewの可視性を設定する場合はvisibilityプロパティを使用し、
Viewの可視性を判定する場合はisVisibleプロパティを使用する。
ただし、特定のケースでは両方を組み合わせて使用することもある。
例えば、可視性を設定する際に他のViewの状態に応じて条件分岐を行い、可視性を判定する場合など。
また、注意点として、visibilityプロパティの変更は実際にUIに反映されるまでに時間がかかる場合があるため、UIの更新が完了するまで待つ必要がある。
それに対して、isVisibleプロパティは即座に現在の可視性を判定する。
あるボタンを押した際、指定のviewが画面の一番上に来る様に、画面をスクロールさせる。
XMLレイアウトファイルで、ScrollView内にスクロール可能なコンテンツを配置。
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/containerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- スクロール可能なコンテンツ -->
</LinearLayout>
</ScrollView>
Kotlinのアクティビティファイル(またはフラグメントファイル)で、ボタンのクリックイベントを処理するためのコードを追加。
val scrollView = findViewById<ScrollView>(R.id.scrollView)
val containerLayout = findViewById<LinearLayout>(R.id.containerLayout)
val targetView = findViewById<View>(R.id.targetView) // スクロールさせたいViewのID
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
scrollView.post {
val targetTop = targetView.top
scrollView.smoothScrollTo(0, targetTop)
}
}
上記のコードは、ボタンをクリックしたときにScrollViewをスクロールさせ、指定したView(targetView)が画面の一番上に表示されるようしている。targetViewは、スクロールさせたいViewのIDに置き換える。
scrollView.postメソッドは、ScrollViewがレイアウト計算を完了してからスクロール処理を行うために使用される。
scrollView.smoothScrollToメソッドは、指定した座標にスクロールするためのメソッド。
これにより、ボタンを押すと指定したViewまでスクロールし、そのViewが画面の一番上に表示される。
adapterにデータを渡す際、 private var data: List = emptyList()の様に、emptyList()を使うのは何故か
初期状態でデータが空のリストであることを示すため。
emptyList()は、Kotlin標準ライブラリで提供される関数であり、要素がない空のリストを生成する。
これにより、アダプターが初期化されたときにデータがない状態であることを明示的に示すことができる。
Null安全性
emptyList()を使用することで、dataプロパティは非nullであり、常に有効なリストオブジェクトを参照していることが保証される。
これにより、nullチェックやヌルポインタエラーを回避することができる。
初期状態の一貫性
初期状態で空のリストを使用することで、アダプターがデータなしで初期化されたことが明確になる。
これにより、アプリケーションの他の部分でデータの有無をチェックする必要がある場合でも、一貫性を保つことができる。
データの追加や変更
emptyList()を使用すると、後からデータを追加または変更することが容易になる。
dataプロパティはvar で宣言されているため、後で新しいリストを代入することができる。
adapter.data = listOf("Item 1", "Item 2", "Item 3") // データを追加
文字列2023-04-01を、04月01日にフォーマットする。
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
val dateString = "2023-04-01"
val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
val date = dateFormat.parse(dateString)
println(date) // 出力: Sun Apr 01 00:00:00 JST 2023
まず日付文字列「2023-04-01」を SimpleDateFormatオブジェクトを使って Dateオブジェクトに変換する。
SimpleDateFormatのパターン文字列には「yyyy-MM-dd」を指定し、与えられた日付文字列と一致するようにする。
変換後、dateにはDateオブジェクトが格納されている。
表示方法や利用方法は、Date オブジェクトのプロパティやメソッドに従って行うことができる。
文字列2023-04-01を、2023/04/01にフォーマットする。
val dateString = "2022-06-01"
val formattedDate = dateString.replace("-", "/")
replaceメソッドを使用して、ハイフン(-)をスラッシュ(/)に置換。これにより、"2022-06-01" が "2022/06/01" に変換される。
数字が四桁になった場合、100の位から、,を入れる。
val number = 1234
val formattedNumber = String.format("%,d", number)
String.formatメソッドを使用して、指定したフォーマットに従って数値を文字列に変換。
%,dというフォーマット指定子を使用することで、3桁ごとにカンマを挿入する。
例1234 は "1,234" という形式でフォーマットされる。