[Cookie Run Cookie情報アプリケーションの作成#4]プロジェクト図面の追加とテスト(1)
前の記事で確認したライブラリを追加しましょう.
プロジェクトの開始
プロジェクトの開始
Empty Activityで始めます안드로이드 8
からバックグラウンド動作が変わったのでMINSdkを28に設定しました
Hilt
まずはhealtそのものを追加しましょう
Project build.gradle
buildscript {
dependencies {
...
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.40.1'
}
}
Module build.gradle
// Module build.gradle
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
...
dependencies {
// Hilt
implementation "com.google.dagger:hilt-android:2.40.1"
kapt "com.google.dagger:hilt-android-compiler:2.40.1"
}
ヒルトを使用するには、アプリケーションに@HiltAndroidApp
アシスタントを追加するだけです.現在アプリケーションクラスはありませんので、MainActivityのある場所にLaunchedApplicationクラスを作成します.
LaunchedApplication.kt
package com.study.cookie
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class LaunchedApplication: Application() {
}
AndroidManifest.xml
...
<application
android:name=".LaunchedApplication"
...
MainActivity.kt
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
LifeCycle & ViewModel
Project build.gradle
dependencies {
...
// ktx
implementation 'androidx.fragment:fragment-ktx:1.4.0'
// LifeCycle
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
// Hilt
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
}
Ktxライブラリが追加されました.これはコートリンデリー格子であるため、by viewModels()
はView Modelに簡単にインポートできます.コードを作成して、正しく適用されているかどうかを見てみましょう.これらのコードは、ライブラリチェック後にすべて削除されるため、どこでも作成できます.MainActivity.kt
の下に全部書きました.package com.study.cookie
import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.*
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Singleton
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launchWhenCreated {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.count.collect {
Log.d("MainActivity", "$it")
}
}
}
}
}
@HiltViewModel
class MainViewModel @Inject constructor(
testRepository: TestRepository
): ViewModel() {
private val _count = MutableStateFlow<Int>(0)
val count = _count.asStateFlow()
init {
viewModelScope.launch {
launch {
val number = testRepository.fetchCount()
_count.value = number
}
launch {
while (true) {
delay(500L)
_count.value++
}
}
}
}
}
class TestRepository {
suspend fun fetchCount(): Int {
delay(2000L)
return 1000
}
}
@Module
@InstallIn(SingletonComponent::class)
class TestModule {
@Singleton
@Provides
fun provideTestRepository(): TestRepository {
return TestRepository()
}
}
Hilt説明
まず、HiltにViewModelを書き込むには、クラスに@HiltViewModel
を貼り付けるだけです.ViewModelはTestRepositoryに依存する.依存関係をHiltに報告する必要があるため、constructor
に@Inject
の説明を追加することができる.
しかし、HiltはこのTestRepositoryをどこから取得するか分からず、このとき利用できるのは@Module
です.
その後、InstallIn
を使用してスキャンを行い、@Singleton
および@Provides
のprovideTestRepository()関数を定義し、InstallIn
の1トンの範囲内でTestRepositoryが必要な場所にDIを割り当てることができる.
コード動作の説明
これで、構築後にアプリケーションを実行すると、Hiltは依存関係に応じて自動的にDIを実行します.MainActivityでby viewModels()
を使用してViewModelを作成すると、init
セクションでTestRepositoryのfetchCountが取得されます.この関数はsuspend fun
であるため停止します.
別のサブルーチンは0.5秒ごとにcount値を増加します.MainActivityはLifecycleScope上でcollect
を実行するため、0.5秒ごとにログをチェックできます.次の2秒以内にfetchCount関数がresumed
の場合、count値は1000に変更されます.
このコードに注意すべき点はrepeatOnLifecycle
です.StateFlow
はエラーライブラリなので、アンドロイドのライフサイクルはわかりません.つまり、Androidバックグラウンド状態に入ってもcollect
を運転し続けます.repeatOnLifecycle
では、バックグラウンドでスレッドを停止し、再起動時にスレッドを再起動できます.
Retrofit2 && Moshi
Project build.gradle
@Module
@InstallIn(SingletonComponent::class)
dependencies {
...
// Retrofit2
implementation "com.squareup.retrofit2:retrofit:2.9.0"
// Moshi
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
}
これはRetrofit2
変換器で、デフォルトのMoshi
とJsonデータをdataclassに変換できます.ここでは通常RxJava3 Adapter
モジュールを一緒に使用する場合もありますが、今回はCoroutine (suspend fun)
を使用します.
FastAPIを使用したAPIサーバの作成で作成したAPIサーバを飛ばし、コードを記述しましょう.class TestModule {
...
@Singleton
@Provides
@Named("localhost")
fun provideLocalhost(): String {
return "192.168.0.7"
}
@Singleton
@Provides
@Named("baseUrl")
fun provideBaseUrl(
@Named("localhost") localhost: String
): String {
return "http://$localhost:8000"
}
@Singleton
@Provides
fun provideMoshiConverterFactory(): MoshiConverterFactory {
return MoshiConverterFactory.create()
}
@Singleton
@Provides
fun provideRetrofit(
moshiConverterFactory: MoshiConverterFactory,
@Named("baseUrl") baseUrl: String
): Retrofit {
return Retrofit.Builder()
.addConverterFactory(moshiConverterFactory)
.baseUrl(baseUrl)
.build()
}
@Singleton
@Provides
fun provideCookieService(retrofit: Retrofit): CookieService {
return retrofit.create(CookieService::class.java)
}
}
原因(シミュレータのlocalhostが違うかもしれません)localhostを使用すると、apiを要求できないという問題が発生し、内部IPを使用して操作しました.Hiltを使用すると、@Named
として動作するDIを指定できますが、このプロジェクトは現在イントラネットを使用しているため、再接続(コンピュータの再起動)のたびにIPが変化するため、分離が必要です.data class CookieInfoList(
@field:Json(name = "list")
val cookieList: List<CookieInfo>,
@field:Json(name = "last")
val last: Int?,
@field:Json(name = "next")
val next: Boolean
)
data class CookieInfo(
@field:Json(name = "cookie_id")
val cookieId: Int,
@field:Json(name = "cookie_name")
val cookieName: String,
@field:Json(name = "cookie_image")
val cookieImage: String
)
interface CookieService {
@GET("/cookie/v1/cookie_id/list")
suspend fun getCookieInfoList(
@Query("start") start: Int,
@Query("length") length: Int
): CookieInfoList
}
その後、データモデルが設定され、@Get
宣言およびsuspend fun
をcoutionとして使用する場合、RxJava
またはenqueue
を使用すると、単一のまたはCallを非表示にすることなく、すぐに値を使用できます(suspend funは停止可能です).viewModelScope.launch {
launch {
while (true) {
delay(1000L)
cookieList.value.last?.let {
val fetchCookieList = testRepository.fetchCookieList(
it,
length = 4
)
_cookieList.value = fetchCookieList
}
}
}
}
後でコード・インスタンスでデータを通信することで、コールバックを必要とせずに同期のようにコードを記述できます.ここで、viewModelScope.launch
はデフォルトでMainThreadであるため、Retrofit 2内部でIOとみなされるため、withContext(Dispatchers.IO)のようなスレッド切り替えが必要であると考えられます.詳細については、TaewhanのCo定例+Retrofit 2ブログを参照してください.
Coil
Project build.gradle
dependencies {
...
// Coil
implementation "io.coil-kt:coil:1.4.0"
}
// MainActivity.kt
viewModel.cookieList.collect { data ->
data.cookieList.forEach {
binding.imageView.load(
uri = uri
)
}
}
内部では、CoolはGlide
よりも軽い画像ライブラリです.ImageView
の拡張関数は、.load
の関数を提供し、画像を簡単に読み込むことができます.
Navigation && SafeArgs
Project build.gradle
// Module build.gradle
buildscript {
dependencies {
...
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
}
}
Project build.gradle
// Module build.gradle
plugin {
....
id 'androidx.navigation.safeargs.kotlin'
}
...
dependencies {
// Navigation
implementation "androidx.navigation:navigation-fragment-ktx:2.3.5"
implementation "androidx.navigation:navigation-ui-ktx:2.3.5"
}
Navigation Settingsから、古いコードをすべて削除して続行します.
MainActivity.kt
package com.study.cookie
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Navigationライブラリを使用して、アクティブなN分割構造を作成します.MainActivity mainに移動します.xmlをバインドします.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
activity_main.xmlファイルにはFragmentContainerViewビューが1つしかありません.重要な点はnav graphであり、以下に説明を添付する.
fragment_first.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="fragment_first"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
fragment_second.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="fragment_second"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout
まず画面切り替えが必要なカットを2つ作ります両者を区別するために、TextViewにxml名を追加しました.
nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/firstFragment">
<fragment
android:id="@+id/firstFragment"
android:name="com.study.cookie.FirstFragment"
android:label="FirstFragment" >
<action
android:id="@+id/action_firstFragment_to_secondFragment"
app:destination="@id/secondFragment" />
</fragment>
<fragment
android:id="@+id/secondFragment"
android:name="com.study.cookie.SecondFragment"
android:label="SecondFragment" >
<argument
android:name="message"
app:argType="string" />
<argument
android:name="something"
app:argType="integer" />
</fragment>
</navigation>
Navigationライブラリには直感的なルーティング関係が表示されます.矢印を使用して分割間の画面切り替えを定義できます.xmlにはaction_firstFragment_to_secondFragment
が追加されています.右側にはArcgumentsとして2つの変数(メッセージ:String、something:Int)があります.
FirstFragment
@AndroidEntryPoint
class FirstFragment : Fragment() {
private lateinit var binding: FragmentFirstBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentFirstBinding.inflate(inflater, container, false)
binding.textView.setOnClickListener {
val directions =
FirstFragmentDirections.actionFirstFragmentToSecondFragment(
message = "navigate",
something = 10
)
it.findNavController().navigate(directions)
}
return binding.root
}
}
Jetpack Navigationライブラリを追加すると、~Directionsが自動的に生成され、Nav Graphで定義されている移動可能なページが表示されます.ここでは、別のセキュリティ・リポジトリをインストールしているので、移動するにはmessageとsomethingを入力する必要があります.
SecondFragment
package com.study.cookie
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.navArgs
import com.study.cookie.databinding.FragmentSecondBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class SecondFragment : Fragment() {
private lateinit var binding: FragmentSecondBinding
private val args: SecondFragmentArgs by navArgs()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentSecondBinding.inflate(inflater, container, false)
binding.textView.text = args.message
return binding.root
}
}
受信側では、navArgs<SecondFragmentArgs>
delegateにメッセージとsomething変数が表示されます.SafeArgsがない場合は、実行時にargs["message"]でチェックする必要がありますが、SafeArgsライブラリを使用してコンパイル時にデータモデルを作成しているため、分割間で安全にデータを転送できます.
Timber
使用するすべてのライブラリ設定が完了しました.(後でテストライブラリを追加)
これで、以前に作成したコードをすべて削除し、プロジェクトを開始します.
Project build.gradle
dependencies {
...
implementation 'com.jakewharton.timber:timber:5.0.1'
}
LaunchedApplication.kt
package com.study.cookie
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
@HiltAndroidApp
class LaunchedApplication: Application() {
override fun onCreate() {
super.onCreate()
Timber.plant(Timber.DebugTree())
}
}
MainActivity.kt
package com.study.cookie
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Timber.d("Hello World!")
}
}
Reference
この問題について([Cookie Run Cookie情報アプリケーションの作成#4]プロジェクト図面の追加とテスト(1)), 我々は、より多くの情報をここで見つけました
https://velog.io/@someh/쿠키런-쿠키-정보-앱-만들기-4-프로젝트-Gradle-추가-및-테스트-1
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
まずはhealtそのものを追加しましょう
Project build.gradle
buildscript {
dependencies {
...
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.40.1'
}
}
Module build.gradle
// Module build.gradle
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
...
dependencies {
// Hilt
implementation "com.google.dagger:hilt-android:2.40.1"
kapt "com.google.dagger:hilt-android-compiler:2.40.1"
}
ヒルトを使用するには、アプリケーションに@HiltAndroidApp
アシスタントを追加するだけです.現在アプリケーションクラスはありませんので、MainActivityのある場所にLaunchedApplicationクラスを作成します.LaunchedApplication.kt
package com.study.cookie
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class LaunchedApplication: Application() {
}
AndroidManifest.xml
...
<application
android:name=".LaunchedApplication"
...
MainActivity.kt
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
LifeCycle & ViewModel
Project build.gradle
dependencies {
...
// ktx
implementation 'androidx.fragment:fragment-ktx:1.4.0'
// LifeCycle
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
// Hilt
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
}
Ktxライブラリが追加されました.これはコートリンデリー格子であるため、by viewModels()
はView Modelに簡単にインポートできます.コードを作成して、正しく適用されているかどうかを見てみましょう.これらのコードは、ライブラリチェック後にすべて削除されるため、どこでも作成できます.MainActivity.kt
の下に全部書きました.package com.study.cookie
import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.*
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Singleton
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launchWhenCreated {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.count.collect {
Log.d("MainActivity", "$it")
}
}
}
}
}
@HiltViewModel
class MainViewModel @Inject constructor(
testRepository: TestRepository
): ViewModel() {
private val _count = MutableStateFlow<Int>(0)
val count = _count.asStateFlow()
init {
viewModelScope.launch {
launch {
val number = testRepository.fetchCount()
_count.value = number
}
launch {
while (true) {
delay(500L)
_count.value++
}
}
}
}
}
class TestRepository {
suspend fun fetchCount(): Int {
delay(2000L)
return 1000
}
}
@Module
@InstallIn(SingletonComponent::class)
class TestModule {
@Singleton
@Provides
fun provideTestRepository(): TestRepository {
return TestRepository()
}
}
Hilt説明
まず、HiltにViewModelを書き込むには、クラスに@HiltViewModel
を貼り付けるだけです.ViewModelはTestRepositoryに依存する.依存関係をHiltに報告する必要があるため、constructor
に@Inject
の説明を追加することができる.
しかし、HiltはこのTestRepositoryをどこから取得するか分からず、このとき利用できるのは@Module
です.
その後、InstallIn
を使用してスキャンを行い、@Singleton
および@Provides
のprovideTestRepository()関数を定義し、InstallIn
の1トンの範囲内でTestRepositoryが必要な場所にDIを割り当てることができる.
コード動作の説明
これで、構築後にアプリケーションを実行すると、Hiltは依存関係に応じて自動的にDIを実行します.MainActivityでby viewModels()
を使用してViewModelを作成すると、init
セクションでTestRepositoryのfetchCountが取得されます.この関数はsuspend fun
であるため停止します.
別のサブルーチンは0.5秒ごとにcount値を増加します.MainActivityはLifecycleScope上でcollect
を実行するため、0.5秒ごとにログをチェックできます.次の2秒以内にfetchCount関数がresumed
の場合、count値は1000に変更されます.
このコードに注意すべき点はrepeatOnLifecycle
です.StateFlow
はエラーライブラリなので、アンドロイドのライフサイクルはわかりません.つまり、Androidバックグラウンド状態に入ってもcollect
を運転し続けます.repeatOnLifecycle
では、バックグラウンドでスレッドを停止し、再起動時にスレッドを再起動できます.
Retrofit2 && Moshi
Project build.gradle
@Module
@InstallIn(SingletonComponent::class)
dependencies {
...
// Retrofit2
implementation "com.squareup.retrofit2:retrofit:2.9.0"
// Moshi
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
}
これはRetrofit2
変換器で、デフォルトのMoshi
とJsonデータをdataclassに変換できます.ここでは通常RxJava3 Adapter
モジュールを一緒に使用する場合もありますが、今回はCoroutine (suspend fun)
を使用します.
FastAPIを使用したAPIサーバの作成で作成したAPIサーバを飛ばし、コードを記述しましょう.class TestModule {
...
@Singleton
@Provides
@Named("localhost")
fun provideLocalhost(): String {
return "192.168.0.7"
}
@Singleton
@Provides
@Named("baseUrl")
fun provideBaseUrl(
@Named("localhost") localhost: String
): String {
return "http://$localhost:8000"
}
@Singleton
@Provides
fun provideMoshiConverterFactory(): MoshiConverterFactory {
return MoshiConverterFactory.create()
}
@Singleton
@Provides
fun provideRetrofit(
moshiConverterFactory: MoshiConverterFactory,
@Named("baseUrl") baseUrl: String
): Retrofit {
return Retrofit.Builder()
.addConverterFactory(moshiConverterFactory)
.baseUrl(baseUrl)
.build()
}
@Singleton
@Provides
fun provideCookieService(retrofit: Retrofit): CookieService {
return retrofit.create(CookieService::class.java)
}
}
原因(シミュレータのlocalhostが違うかもしれません)localhostを使用すると、apiを要求できないという問題が発生し、内部IPを使用して操作しました.Hiltを使用すると、@Named
として動作するDIを指定できますが、このプロジェクトは現在イントラネットを使用しているため、再接続(コンピュータの再起動)のたびにIPが変化するため、分離が必要です.data class CookieInfoList(
@field:Json(name = "list")
val cookieList: List<CookieInfo>,
@field:Json(name = "last")
val last: Int?,
@field:Json(name = "next")
val next: Boolean
)
data class CookieInfo(
@field:Json(name = "cookie_id")
val cookieId: Int,
@field:Json(name = "cookie_name")
val cookieName: String,
@field:Json(name = "cookie_image")
val cookieImage: String
)
interface CookieService {
@GET("/cookie/v1/cookie_id/list")
suspend fun getCookieInfoList(
@Query("start") start: Int,
@Query("length") length: Int
): CookieInfoList
}
その後、データモデルが設定され、@Get
宣言およびsuspend fun
をcoutionとして使用する場合、RxJava
またはenqueue
を使用すると、単一のまたはCallを非表示にすることなく、すぐに値を使用できます(suspend funは停止可能です).viewModelScope.launch {
launch {
while (true) {
delay(1000L)
cookieList.value.last?.let {
val fetchCookieList = testRepository.fetchCookieList(
it,
length = 4
)
_cookieList.value = fetchCookieList
}
}
}
}
後でコード・インスタンスでデータを通信することで、コールバックを必要とせずに同期のようにコードを記述できます.ここで、viewModelScope.launch
はデフォルトでMainThreadであるため、Retrofit 2内部でIOとみなされるため、withContext(Dispatchers.IO)のようなスレッド切り替えが必要であると考えられます.詳細については、TaewhanのCo定例+Retrofit 2ブログを参照してください.
Coil
Project build.gradle
dependencies {
...
// Coil
implementation "io.coil-kt:coil:1.4.0"
}
// MainActivity.kt
viewModel.cookieList.collect { data ->
data.cookieList.forEach {
binding.imageView.load(
uri = uri
)
}
}
内部では、CoolはGlide
よりも軽い画像ライブラリです.ImageView
の拡張関数は、.load
の関数を提供し、画像を簡単に読み込むことができます.
Navigation && SafeArgs
Project build.gradle
// Module build.gradle
buildscript {
dependencies {
...
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
}
}
Project build.gradle
// Module build.gradle
plugin {
....
id 'androidx.navigation.safeargs.kotlin'
}
...
dependencies {
// Navigation
implementation "androidx.navigation:navigation-fragment-ktx:2.3.5"
implementation "androidx.navigation:navigation-ui-ktx:2.3.5"
}
Navigation Settingsから、古いコードをすべて削除して続行します.
MainActivity.kt
package com.study.cookie
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Navigationライブラリを使用して、アクティブなN分割構造を作成します.MainActivity mainに移動します.xmlをバインドします.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
activity_main.xmlファイルにはFragmentContainerViewビューが1つしかありません.重要な点はnav graphであり、以下に説明を添付する.
fragment_first.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="fragment_first"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
fragment_second.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="fragment_second"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout
まず画面切り替えが必要なカットを2つ作ります両者を区別するために、TextViewにxml名を追加しました.
nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/firstFragment">
<fragment
android:id="@+id/firstFragment"
android:name="com.study.cookie.FirstFragment"
android:label="FirstFragment" >
<action
android:id="@+id/action_firstFragment_to_secondFragment"
app:destination="@id/secondFragment" />
</fragment>
<fragment
android:id="@+id/secondFragment"
android:name="com.study.cookie.SecondFragment"
android:label="SecondFragment" >
<argument
android:name="message"
app:argType="string" />
<argument
android:name="something"
app:argType="integer" />
</fragment>
</navigation>
Navigationライブラリには直感的なルーティング関係が表示されます.矢印を使用して分割間の画面切り替えを定義できます.xmlにはaction_firstFragment_to_secondFragment
が追加されています.右側にはArcgumentsとして2つの変数(メッセージ:String、something:Int)があります.
FirstFragment
@AndroidEntryPoint
class FirstFragment : Fragment() {
private lateinit var binding: FragmentFirstBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentFirstBinding.inflate(inflater, container, false)
binding.textView.setOnClickListener {
val directions =
FirstFragmentDirections.actionFirstFragmentToSecondFragment(
message = "navigate",
something = 10
)
it.findNavController().navigate(directions)
}
return binding.root
}
}
Jetpack Navigationライブラリを追加すると、~Directionsが自動的に生成され、Nav Graphで定義されている移動可能なページが表示されます.ここでは、別のセキュリティ・リポジトリをインストールしているので、移動するにはmessageとsomethingを入力する必要があります.
SecondFragment
package com.study.cookie
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.navArgs
import com.study.cookie.databinding.FragmentSecondBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class SecondFragment : Fragment() {
private lateinit var binding: FragmentSecondBinding
private val args: SecondFragmentArgs by navArgs()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentSecondBinding.inflate(inflater, container, false)
binding.textView.text = args.message
return binding.root
}
}
受信側では、navArgs<SecondFragmentArgs>
delegateにメッセージとsomething変数が表示されます.SafeArgsがない場合は、実行時にargs["message"]でチェックする必要がありますが、SafeArgsライブラリを使用してコンパイル時にデータモデルを作成しているため、分割間で安全にデータを転送できます.
Timber
使用するすべてのライブラリ設定が完了しました.(後でテストライブラリを追加)
これで、以前に作成したコードをすべて削除し、プロジェクトを開始します.
Project build.gradle
dependencies {
...
implementation 'com.jakewharton.timber:timber:5.0.1'
}
LaunchedApplication.kt
package com.study.cookie
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
@HiltAndroidApp
class LaunchedApplication: Application() {
override fun onCreate() {
super.onCreate()
Timber.plant(Timber.DebugTree())
}
}
MainActivity.kt
package com.study.cookie
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Timber.d("Hello World!")
}
}
Reference
この問題について([Cookie Run Cookie情報アプリケーションの作成#4]プロジェクト図面の追加とテスト(1)), 我々は、より多くの情報をここで見つけました
https://velog.io/@someh/쿠키런-쿠키-정보-앱-만들기-4-프로젝트-Gradle-추가-및-테스트-1
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
dependencies {
...
// ktx
implementation 'androidx.fragment:fragment-ktx:1.4.0'
// LifeCycle
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
// Hilt
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
}
package com.study.cookie
import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.*
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Singleton
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launchWhenCreated {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.count.collect {
Log.d("MainActivity", "$it")
}
}
}
}
}
@HiltViewModel
class MainViewModel @Inject constructor(
testRepository: TestRepository
): ViewModel() {
private val _count = MutableStateFlow<Int>(0)
val count = _count.asStateFlow()
init {
viewModelScope.launch {
launch {
val number = testRepository.fetchCount()
_count.value = number
}
launch {
while (true) {
delay(500L)
_count.value++
}
}
}
}
}
class TestRepository {
suspend fun fetchCount(): Int {
delay(2000L)
return 1000
}
}
@Module
@InstallIn(SingletonComponent::class)
class TestModule {
@Singleton
@Provides
fun provideTestRepository(): TestRepository {
return TestRepository()
}
}
Project build.gradle
@Module
@InstallIn(SingletonComponent::class)
dependencies {
...
// Retrofit2
implementation "com.squareup.retrofit2:retrofit:2.9.0"
// Moshi
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
}
これはRetrofit2
変換器で、デフォルトのMoshi
とJsonデータをdataclassに変換できます.ここでは通常RxJava3 Adapter
モジュールを一緒に使用する場合もありますが、今回はCoroutine (suspend fun)
を使用します.FastAPIを使用したAPIサーバの作成で作成したAPIサーバを飛ばし、コードを記述しましょう.
class TestModule {
...
@Singleton
@Provides
@Named("localhost")
fun provideLocalhost(): String {
return "192.168.0.7"
}
@Singleton
@Provides
@Named("baseUrl")
fun provideBaseUrl(
@Named("localhost") localhost: String
): String {
return "http://$localhost:8000"
}
@Singleton
@Provides
fun provideMoshiConverterFactory(): MoshiConverterFactory {
return MoshiConverterFactory.create()
}
@Singleton
@Provides
fun provideRetrofit(
moshiConverterFactory: MoshiConverterFactory,
@Named("baseUrl") baseUrl: String
): Retrofit {
return Retrofit.Builder()
.addConverterFactory(moshiConverterFactory)
.baseUrl(baseUrl)
.build()
}
@Singleton
@Provides
fun provideCookieService(retrofit: Retrofit): CookieService {
return retrofit.create(CookieService::class.java)
}
}
原因(シミュレータのlocalhostが違うかもしれません)localhostを使用すると、apiを要求できないという問題が発生し、内部IPを使用して操作しました.Hiltを使用すると、@Named
として動作するDIを指定できますが、このプロジェクトは現在イントラネットを使用しているため、再接続(コンピュータの再起動)のたびにIPが変化するため、分離が必要です.data class CookieInfoList(
@field:Json(name = "list")
val cookieList: List<CookieInfo>,
@field:Json(name = "last")
val last: Int?,
@field:Json(name = "next")
val next: Boolean
)
data class CookieInfo(
@field:Json(name = "cookie_id")
val cookieId: Int,
@field:Json(name = "cookie_name")
val cookieName: String,
@field:Json(name = "cookie_image")
val cookieImage: String
)
interface CookieService {
@GET("/cookie/v1/cookie_id/list")
suspend fun getCookieInfoList(
@Query("start") start: Int,
@Query("length") length: Int
): CookieInfoList
}
その後、データモデルが設定され、@Get
宣言およびsuspend fun
をcoutionとして使用する場合、RxJava
またはenqueue
を使用すると、単一のviewModelScope.launch {
launch {
while (true) {
delay(1000L)
cookieList.value.last?.let {
val fetchCookieList = testRepository.fetchCookieList(
it,
length = 4
)
_cookieList.value = fetchCookieList
}
}
}
}
後でコード・インスタンスでデータを通信することで、コールバックを必要とせずに同期のようにコードを記述できます.ここで、viewModelScope.launch
はデフォルトでMainThreadであるため、Retrofit 2内部でIOとみなされるため、withContext(Dispatchers.IO)のようなスレッド切り替えが必要であると考えられます.詳細については、TaewhanのCo定例+Retrofit 2ブログを参照してください.Coil
Project build.gradle
dependencies {
...
// Coil
implementation "io.coil-kt:coil:1.4.0"
}
// MainActivity.kt
viewModel.cookieList.collect { data ->
data.cookieList.forEach {
binding.imageView.load(
uri = uri
)
}
}
内部では、CoolはGlide
よりも軽い画像ライブラリです.ImageView
の拡張関数は、.load
の関数を提供し、画像を簡単に読み込むことができます.
Navigation && SafeArgs
Project build.gradle
// Module build.gradle
buildscript {
dependencies {
...
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
}
}
Project build.gradle
// Module build.gradle
plugin {
....
id 'androidx.navigation.safeargs.kotlin'
}
...
dependencies {
// Navigation
implementation "androidx.navigation:navigation-fragment-ktx:2.3.5"
implementation "androidx.navigation:navigation-ui-ktx:2.3.5"
}
Navigation Settingsから、古いコードをすべて削除して続行します.
MainActivity.kt
package com.study.cookie
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Navigationライブラリを使用して、アクティブなN分割構造を作成します.MainActivity mainに移動します.xmlをバインドします.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
activity_main.xmlファイルにはFragmentContainerViewビューが1つしかありません.重要な点はnav graphであり、以下に説明を添付する.
fragment_first.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="fragment_first"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
fragment_second.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="fragment_second"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout
まず画面切り替えが必要なカットを2つ作ります両者を区別するために、TextViewにxml名を追加しました.
nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/firstFragment">
<fragment
android:id="@+id/firstFragment"
android:name="com.study.cookie.FirstFragment"
android:label="FirstFragment" >
<action
android:id="@+id/action_firstFragment_to_secondFragment"
app:destination="@id/secondFragment" />
</fragment>
<fragment
android:id="@+id/secondFragment"
android:name="com.study.cookie.SecondFragment"
android:label="SecondFragment" >
<argument
android:name="message"
app:argType="string" />
<argument
android:name="something"
app:argType="integer" />
</fragment>
</navigation>
Navigationライブラリには直感的なルーティング関係が表示されます.矢印を使用して分割間の画面切り替えを定義できます.xmlにはaction_firstFragment_to_secondFragment
が追加されています.右側にはArcgumentsとして2つの変数(メッセージ:String、something:Int)があります.
FirstFragment
@AndroidEntryPoint
class FirstFragment : Fragment() {
private lateinit var binding: FragmentFirstBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentFirstBinding.inflate(inflater, container, false)
binding.textView.setOnClickListener {
val directions =
FirstFragmentDirections.actionFirstFragmentToSecondFragment(
message = "navigate",
something = 10
)
it.findNavController().navigate(directions)
}
return binding.root
}
}
Jetpack Navigationライブラリを追加すると、~Directionsが自動的に生成され、Nav Graphで定義されている移動可能なページが表示されます.ここでは、別のセキュリティ・リポジトリをインストールしているので、移動するにはmessageとsomethingを入力する必要があります.
SecondFragment
package com.study.cookie
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.navArgs
import com.study.cookie.databinding.FragmentSecondBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class SecondFragment : Fragment() {
private lateinit var binding: FragmentSecondBinding
private val args: SecondFragmentArgs by navArgs()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentSecondBinding.inflate(inflater, container, false)
binding.textView.text = args.message
return binding.root
}
}
受信側では、navArgs<SecondFragmentArgs>
delegateにメッセージとsomething変数が表示されます.SafeArgsがない場合は、実行時にargs["message"]でチェックする必要がありますが、SafeArgsライブラリを使用してコンパイル時にデータモデルを作成しているため、分割間で安全にデータを転送できます.
Timber
使用するすべてのライブラリ設定が完了しました.(後でテストライブラリを追加)
これで、以前に作成したコードをすべて削除し、プロジェクトを開始します.
Project build.gradle
dependencies {
...
implementation 'com.jakewharton.timber:timber:5.0.1'
}
LaunchedApplication.kt
package com.study.cookie
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
@HiltAndroidApp
class LaunchedApplication: Application() {
override fun onCreate() {
super.onCreate()
Timber.plant(Timber.DebugTree())
}
}
MainActivity.kt
package com.study.cookie
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Timber.d("Hello World!")
}
}
dependencies {
...
// Coil
implementation "io.coil-kt:coil:1.4.0"
}
// MainActivity.kt
viewModel.cookieList.collect { data ->
data.cookieList.forEach {
binding.imageView.load(
uri = uri
)
}
}
Project build.gradle
// Module build.gradle
buildscript {
dependencies {
...
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
}
}
Project build.gradle
// Module build.gradle
plugin {
....
id 'androidx.navigation.safeargs.kotlin'
}
...
dependencies {
// Navigation
implementation "androidx.navigation:navigation-fragment-ktx:2.3.5"
implementation "androidx.navigation:navigation-ui-ktx:2.3.5"
}
Navigation Settingsから、古いコードをすべて削除して続行します.MainActivity.kt
package com.study.cookie
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Navigationライブラリを使用して、アクティブなN分割構造を作成します.MainActivity mainに移動します.xmlをバインドします.activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
activity_main.xmlファイルにはFragmentContainerViewビューが1つしかありません.重要な点はnav graphであり、以下に説明を添付する.fragment_first.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="fragment_first"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
fragment_second.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="fragment_second"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout
まず画面切り替えが必要なカットを2つ作ります両者を区別するために、TextViewにxml名を追加しました.nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/firstFragment">
<fragment
android:id="@+id/firstFragment"
android:name="com.study.cookie.FirstFragment"
android:label="FirstFragment" >
<action
android:id="@+id/action_firstFragment_to_secondFragment"
app:destination="@id/secondFragment" />
</fragment>
<fragment
android:id="@+id/secondFragment"
android:name="com.study.cookie.SecondFragment"
android:label="SecondFragment" >
<argument
android:name="message"
app:argType="string" />
<argument
android:name="something"
app:argType="integer" />
</fragment>
</navigation>
Navigationライブラリには直感的なルーティング関係が表示されます.矢印を使用して分割間の画面切り替えを定義できます.xmlには
action_firstFragment_to_secondFragment
が追加されています.右側にはArcgumentsとして2つの変数(メッセージ:String、something:Int)があります.FirstFragment
@AndroidEntryPoint
class FirstFragment : Fragment() {
private lateinit var binding: FragmentFirstBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentFirstBinding.inflate(inflater, container, false)
binding.textView.setOnClickListener {
val directions =
FirstFragmentDirections.actionFirstFragmentToSecondFragment(
message = "navigate",
something = 10
)
it.findNavController().navigate(directions)
}
return binding.root
}
}
Jetpack Navigationライブラリを追加すると、~Directionsが自動的に生成され、Nav Graphで定義されている移動可能なページが表示されます.ここでは、別のセキュリティ・リポジトリをインストールしているので、移動するにはmessageとsomethingを入力する必要があります.SecondFragment
package com.study.cookie
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.navArgs
import com.study.cookie.databinding.FragmentSecondBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class SecondFragment : Fragment() {
private lateinit var binding: FragmentSecondBinding
private val args: SecondFragmentArgs by navArgs()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentSecondBinding.inflate(inflater, container, false)
binding.textView.text = args.message
return binding.root
}
}
受信側では、navArgs<SecondFragmentArgs>
delegateにメッセージとsomething変数が表示されます.SafeArgsがない場合は、実行時にargs["message"]でチェックする必要がありますが、SafeArgsライブラリを使用してコンパイル時にデータモデルを作成しているため、分割間で安全にデータを転送できます.Timber
使用するすべてのライブラリ設定が完了しました.(後でテストライブラリを追加)
これで、以前に作成したコードをすべて削除し、プロジェクトを開始します.
Project build.gradle
dependencies {
...
implementation 'com.jakewharton.timber:timber:5.0.1'
}
LaunchedApplication.kt
package com.study.cookie
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
@HiltAndroidApp
class LaunchedApplication: Application() {
override fun onCreate() {
super.onCreate()
Timber.plant(Timber.DebugTree())
}
}
MainActivity.kt
package com.study.cookie
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Timber.d("Hello World!")
}
}
dependencies {
...
implementation 'com.jakewharton.timber:timber:5.0.1'
}
package com.study.cookie
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
@HiltAndroidApp
class LaunchedApplication: Application() {
override fun onCreate() {
super.onCreate()
Timber.plant(Timber.DebugTree())
}
}
package com.study.cookie
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Timber.d("Hello World!")
}
}
Reference
この問題について([Cookie Run Cookie情報アプリケーションの作成#4]プロジェクト図面の追加とテスト(1)), 我々は、より多くの情報をここで見つけました https://velog.io/@someh/쿠키런-쿠키-정보-앱-만들기-4-프로젝트-Gradle-추가-및-테스트-1テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol