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" という形式でフォーマットされる。