diff --git a/README.md b/README.md index 91740b7..46637ed 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ ### 環境 - IDE:Android Studio Ladybug | 2024.2.1 Patch 3 -- Kotlin: 2.0.0 +- Kotlin: 2.0.21 - Java:17 - Gradle:8.9 - minSdk:23 diff --git a/app/build.gradle b/app/build.gradle index 1d1e3f1..abdc496 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,6 +5,7 @@ plugins { id 'kotlin-parcelize' id 'androidx.navigation.safeargs.kotlin' id("org.jlleitschuh.gradle.ktlint") version "12.1.2" + id 'com.google.dagger.hilt.android' } android { @@ -28,11 +29,11 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = '17' + jvmTarget = '1.8' } buildFeatures { viewBinding true @@ -63,6 +64,8 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.2.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + implementation "com.google.dagger:hilt-android:2.55" + kapt "com.google.dagger:hilt-compiler:2.55" // Retrofit implementation("com.squareup.retrofit2:converter-moshi:2.9.0") @@ -70,3 +73,7 @@ dependencies { implementation("com.squareup.retrofit2:retrofit:2.11.0") implementation("com.squareup.moshi:moshi-kotlin:1.14.0") } + +kapt { + correctErrorTypes true +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 85fb0a2..2e1287a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,7 +11,8 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.AndroidEngineerCodeCheck" - android:fullBackupContent="@xml/backup_descriptor"> + android:fullBackupContent="@xml/backup_descriptor" + android:name=".CodeCheckApplication"> diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/CodeCheckApplication.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/CodeCheckApplication.kt new file mode 100644 index 0000000..39efc83 --- /dev/null +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/CodeCheckApplication.kt @@ -0,0 +1,7 @@ +package jp.co.yumemi.android.code_check + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class CodeCheckApplication : Application() diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/CodeCheckModule.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/CodeCheckModule.kt new file mode 100644 index 0000000..486fcf9 --- /dev/null +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/CodeCheckModule.kt @@ -0,0 +1,38 @@ +package jp.co.yumemi.android.code_check + +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import jp.co.yumemi.android.code_check.features.github.api.GitHubServiceApi +import jp.co.yumemi.android.code_check.features.github.api.GitHubServiceApiImpl +import jp.co.yumemi.android.code_check.features.github.reposiotory.GitHubServiceRepository +import jp.co.yumemi.android.code_check.features.github.reposiotory.GitHubServiceRepositoryImpl +import jp.co.yumemi.android.code_check.features.github.usecase.GitHubServiceUsecase +import jp.co.yumemi.android.code_check.features.github.usecase.GitHubServiceUsecaseImpl +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class GitHubUsecaseModule { + @Singleton + @Binds + abstract fun provideGitHubServiceUsecase(impl: GitHubServiceUsecaseImpl): GitHubServiceUsecase +} + +@Module +@InstallIn(SingletonComponent::class) +abstract class GitHubRepositoryModule { + @Singleton + @Binds + abstract fun provideGitHubServiceRepository(impl: GitHubServiceRepositoryImpl): GitHubServiceRepository + + companion object { + @Provides + @Singleton + fun provideGitHubServiceApi(): GitHubServiceApi { + return GitHubServiceApiImpl() + } + } +} diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/RepositorySearchViewModel.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/RepositorySearchViewModel.kt deleted file mode 100644 index 5f764f1..0000000 --- a/app/src/main/kotlin/jp/co/yumemi/android/code_check/RepositorySearchViewModel.kt +++ /dev/null @@ -1,52 +0,0 @@ -package jp.co.yumemi.android.code_check - -import android.app.Application -import android.util.Log -import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope -import jp.co.yumemi.android.code_check.features.github.reposiotory.NetworkException -import jp.co.yumemi.android.code_check.features.github.reposiotory.NetworkRepository -import jp.co.yumemi.android.code_check.features.github.reposiotory.NetworkResult -import kotlinx.coroutines.launch - -/** - * RepositorySearchFragmentで利用するリポジトリ検索用のViewModel - */ -class RepositorySearchViewModel(application: Application) : AndroidViewModel(application) { - private val appContext = application - private val networkRepository = NetworkRepository() - - private val _errorMessage = MutableLiveData() - val errorMessage: LiveData get() = _errorMessage - - private val _searchResults = MutableLiveData>() - val searchResults: LiveData> get() = _searchResults - - /** - * GitHubのレポジトリ検索を行う - * @param query 検索キーワード - */ - fun searchRepositories(query: String) { - if (query.isBlank()) { - _errorMessage.postValue("検索キーワードを入力してください。") - return - } - viewModelScope.launch { - try { - val results = networkRepository.fetchSearchResults(query, appContext) - if (results is NetworkResult.Error) { - _errorMessage.postValue(results.exception.message) - return@launch - } - if (results is NetworkResult.Success) { - _searchResults.postValue(results.data) - } - } catch (e: NetworkException) { - Log.e("NetworkException", e.message, e) - _errorMessage.postValue(e.message) - } - } - } -} diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/TopActivity.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/TopActivity.kt index d6cc54e..926636a 100644 --- a/app/src/main/kotlin/jp/co/yumemi/android/code_check/TopActivity.kt +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/TopActivity.kt @@ -4,5 +4,7 @@ package jp.co.yumemi.android.code_check import androidx.appcompat.app.AppCompatActivity +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class TopActivity : AppCompatActivity(R.layout.activity_top) diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/core/api/NetworkConnectivityService.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/core/api/NetworkConnectivityService.kt new file mode 100644 index 0000000..d11f4f9 --- /dev/null +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/core/api/NetworkConnectivityService.kt @@ -0,0 +1,22 @@ +package jp.co.yumemi.android.code_check.core.api + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class NetworkConnectivityService + @Inject + constructor( + @ApplicationContext private val context: Context, + ) { + fun isNetworkAvailable(): Boolean { + val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val network = connectivityManager.activeNetwork ?: return false + val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false + return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + } + } diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/RepositoryItem.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/core/entity/RepositoryItem.kt similarity index 85% rename from app/src/main/kotlin/jp/co/yumemi/android/code_check/RepositoryItem.kt rename to app/src/main/kotlin/jp/co/yumemi/android/code_check/core/entity/RepositoryItem.kt index fd37b26..1136dce 100644 --- a/app/src/main/kotlin/jp/co/yumemi/android/code_check/RepositoryItem.kt +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/core/entity/RepositoryItem.kt @@ -1,4 +1,4 @@ -package jp.co.yumemi.android.code_check +package jp.co.yumemi.android.code_check.core.entity import android.os.Parcelable import kotlinx.parcelize.Parcelize diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/RepositoryDetailFragment.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/core/presenter/detail/RepositoryDetailFragment.kt similarity index 87% rename from app/src/main/kotlin/jp/co/yumemi/android/code_check/RepositoryDetailFragment.kt rename to app/src/main/kotlin/jp/co/yumemi/android/code_check/core/presenter/detail/RepositoryDetailFragment.kt index aee80a0..614ee71 100644 --- a/app/src/main/kotlin/jp/co/yumemi/android/code_check/RepositoryDetailFragment.kt +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/core/presenter/detail/RepositoryDetailFragment.kt @@ -1,15 +1,19 @@ /* * Copyright © 2021 YUMEMI Inc. All rights reserved. */ -package jp.co.yumemi.android.code_check +package jp.co.yumemi.android.code_check.core.presenter.detail import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment import androidx.navigation.fragment.navArgs import coil.load +import dagger.hilt.android.AndroidEntryPoint +import jp.co.yumemi.android.code_check.R +import jp.co.yumemi.android.code_check.core.entity.RepositoryItem import jp.co.yumemi.android.code_check.databinding.FragmentRepositoryDetailBinding +@AndroidEntryPoint class RepositoryDetailFragment : Fragment(R.layout.fragment_repository_detail) { private val args: RepositoryDetailFragmentArgs by navArgs() diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/RepositoryListRecyclerViewAdapter.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/core/presenter/search/RepositoryListRecyclerViewAdapter.kt similarity index 92% rename from app/src/main/kotlin/jp/co/yumemi/android/code_check/RepositoryListRecyclerViewAdapter.kt rename to app/src/main/kotlin/jp/co/yumemi/android/code_check/core/presenter/search/RepositoryListRecyclerViewAdapter.kt index ef587e4..f0d1656 100644 --- a/app/src/main/kotlin/jp/co/yumemi/android/code_check/RepositoryListRecyclerViewAdapter.kt +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/core/presenter/search/RepositoryListRecyclerViewAdapter.kt @@ -1,4 +1,4 @@ -package jp.co.yumemi.android.code_check +package jp.co.yumemi.android.code_check.core.presenter.search import android.view.LayoutInflater import android.view.View @@ -7,6 +7,8 @@ import android.widget.TextView import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import jp.co.yumemi.android.code_check.R +import jp.co.yumemi.android.code_check.core.entity.RepositoryItem /** * DiffUtilの実装 diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/RepositorySearchFragment.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/core/presenter/search/RepositorySearchFragment.kt similarity index 78% rename from app/src/main/kotlin/jp/co/yumemi/android/code_check/RepositorySearchFragment.kt rename to app/src/main/kotlin/jp/co/yumemi/android/code_check/core/presenter/search/RepositorySearchFragment.kt index 7cb77e9..12a133c 100644 --- a/app/src/main/kotlin/jp/co/yumemi/android/code_check/RepositorySearchFragment.kt +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/core/presenter/search/RepositorySearchFragment.kt @@ -1,22 +1,24 @@ -/* - * Copyright © 2021 YUMEMI Inc. All rights reserved. - */ -package jp.co.yumemi.android.code_check +package jp.co.yumemi.android.code_check.core.presenter.search import android.os.Bundle import android.view.View import android.view.inputmethod.EditorInfo import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProvider +import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint +import jp.co.yumemi.android.code_check.R +import jp.co.yumemi.android.code_check.core.entity.RepositoryItem +import jp.co.yumemi.android.code_check.core.utils.DialogHelper import jp.co.yumemi.android.code_check.databinding.FragmentRepositorySearchBinding +@AndroidEntryPoint class RepositorySearchFragment : Fragment(R.layout.fragment_repository_search) { private var _binding: FragmentRepositorySearchBinding? = null private val binding get() = _binding ?: throw IllegalStateException("Binding is null") - private lateinit var viewModel: RepositorySearchViewModel + private val viewModel: RepositorySearchViewModel by viewModels() private val adapter by lazy { RepositoryListRecyclerViewAdapter( @@ -34,7 +36,6 @@ class RepositorySearchFragment : Fragment(R.layout.fragment_repository_search) { ) { super.onViewCreated(view, savedInstanceState) _binding = FragmentRepositorySearchBinding.bind(view) - viewModel = ViewModelProvider(this)[RepositorySearchViewModel::class.java] observeViewModel() setupRecyclerView() @@ -46,13 +47,19 @@ class RepositorySearchFragment : Fragment(R.layout.fragment_repository_search) { adapter.submitList(it) } viewModel.errorMessage.observe(viewLifecycleOwner) { - it?.let { DialogHelper.showErrorDialog(requireContext(), it) } + it?.let { + DialogHelper.showErrorDialog( + requireContext(), + requireContext().getString(it), + ) + } } } private fun setupRecyclerView() { val layoutManager = LinearLayoutManager(requireContext()) - val dividerItemDecoration = DividerItemDecoration(requireContext(), layoutManager.orientation) + val dividerItemDecoration = + DividerItemDecoration(requireContext(), layoutManager.orientation) binding.recyclerView.apply { this.layoutManager = layoutManager diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/core/presenter/search/RepositorySearchViewModel.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/core/presenter/search/RepositorySearchViewModel.kt new file mode 100644 index 0000000..d19b58f --- /dev/null +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/core/presenter/search/RepositorySearchViewModel.kt @@ -0,0 +1,73 @@ +package jp.co.yumemi.android.code_check.core.presenter.search + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import jp.co.yumemi.android.code_check.R +import jp.co.yumemi.android.code_check.core.entity.RepositoryItem +import jp.co.yumemi.android.code_check.features.github.reposiotory.NetworkException +import jp.co.yumemi.android.code_check.features.github.usecase.GitHubServiceUsecase +import jp.co.yumemi.android.code_check.features.github.utils.GitHubError +import jp.co.yumemi.android.code_check.features.github.utils.NetworkResult +import kotlinx.coroutines.launch +import javax.inject.Inject + +/** + * RepositorySearchFragmentで利用するリポジトリ検索用のViewModel + */ +@HiltViewModel +class RepositorySearchViewModel + @Inject + constructor( + private val networkRepository: GitHubServiceUsecase, + ) : ViewModel() { + private val _errorMessage = MutableLiveData() + val errorMessage: LiveData get() = _errorMessage + + private val _searchResults = MutableLiveData>() + val searchResults: LiveData> get() = _searchResults + + /** + * GitHubのレポジトリ検索を行う + * @param query 検索キーワード + */ + fun searchRepositories(query: String) { + if (query.isBlank()) { + _errorMessage.postValue(R.string.form_is_empty) + return + } + viewModelScope.launch { + try { + val results = networkRepository.fetchSearchResults(query) + if (results is NetworkResult.Error) { + handleError(results.exception) + return@launch + } + if (results is NetworkResult.Success) { + _searchResults.postValue(results.data) + } + } catch (e: NetworkException) { + Log.e("NetworkException", e.message, e) + handleError(GitHubError.NetworkError(e)) + } + } + } + + /** + * エラーが発生した時に、Viewに問題を表示するためのもの + * @param error エラー情報 + */ + private fun handleError(error: GitHubError) { + _errorMessage.value = + when (error) { + is GitHubError.NetworkError -> R.string.network_error + is GitHubError.ApiError -> R.string.api_error + is GitHubError.ParseError -> R.string.parse_error + is GitHubError.RateLimitError -> R.string.rate_limit_error + is GitHubError.AuthenticationError -> R.string.auth_error + } + } + } diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/DialogHelper.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/core/utils/DialogHelper.kt similarity index 87% rename from app/src/main/kotlin/jp/co/yumemi/android/code_check/DialogHelper.kt rename to app/src/main/kotlin/jp/co/yumemi/android/code_check/core/utils/DialogHelper.kt index 332118f..5798c1b 100644 --- a/app/src/main/kotlin/jp/co/yumemi/android/code_check/DialogHelper.kt +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/core/utils/DialogHelper.kt @@ -1,4 +1,4 @@ -package jp.co.yumemi.android.code_check +package jp.co.yumemi.android.code_check.core.utils import android.app.AlertDialog import android.content.Context diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubRepositoryApi.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubServiceApi.kt similarity index 86% rename from app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubRepositoryApi.kt rename to app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubServiceApi.kt index 4066a49..8a51f27 100644 --- a/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubRepositoryApi.kt +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubServiceApi.kt @@ -2,6 +2,6 @@ package jp.co.yumemi.android.code_check.features.github.api import jp.co.yumemi.android.code_check.features.github.entity.RepositoryList -interface GitHubRepositoryApi { +interface GitHubServiceApi { suspend fun getRepository(searchWord: String): RepositoryList } diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubRepositoryApiBuilderInterface.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubServiceApiBuilderInterface.kt similarity index 88% rename from app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubRepositoryApiBuilderInterface.kt rename to app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubServiceApiBuilderInterface.kt index a63ec4f..fcc38eb 100644 --- a/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubRepositoryApiBuilderInterface.kt +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubServiceApiBuilderInterface.kt @@ -5,7 +5,7 @@ import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Query -interface GitHubRepositoryApiBuilderInterface { +interface GitHubServiceApiBuilderInterface { @GET("/search/repositories") suspend fun getRepository( @Query("q") searchWord: String, diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubRepositoryApiImpl.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubServiceApiImpl.kt similarity index 94% rename from app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubRepositoryApiImpl.kt rename to app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubServiceApiImpl.kt index bf8f356..e1997e0 100644 --- a/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubRepositoryApiImpl.kt +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/api/GitHubServiceApiImpl.kt @@ -10,7 +10,7 @@ import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory -class GitHubRepositoryApiImpl : GitHubRepositoryApi { +class GitHubServiceApiImpl : GitHubServiceApi { companion object { val client = OkHttpClient.Builder() @@ -37,7 +37,7 @@ class GitHubRepositoryApiImpl : GitHubRepositoryApi { .client(client) .addConverterFactory(MoshiConverterFactory.create(moshi)) .build() - .create(GitHubRepositoryApiBuilderInterface::class.java) + .create(GitHubServiceApiBuilderInterface::class.java) } override suspend fun getRepository(searchWord: String): RepositoryList { diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/entity/GitHubRepositoryEntity.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/entity/GitHubServiceEntity.kt similarity index 100% rename from app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/entity/GitHubRepositoryEntity.kt rename to app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/entity/GitHubServiceEntity.kt diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/reposiotory/GitHubServiceRepository.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/reposiotory/GitHubServiceRepository.kt new file mode 100644 index 0000000..0ccedef --- /dev/null +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/reposiotory/GitHubServiceRepository.kt @@ -0,0 +1,8 @@ +package jp.co.yumemi.android.code_check.features.github.reposiotory + +import jp.co.yumemi.android.code_check.core.entity.RepositoryItem +import jp.co.yumemi.android.code_check.features.github.utils.NetworkResult + +interface GitHubServiceRepository { + suspend fun fetchSearchResults(inputText: String): NetworkResult> +} diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/reposiotory/GitHubServiceRepositoryImpl.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/reposiotory/GitHubServiceRepositoryImpl.kt new file mode 100644 index 0000000..ecec1d2 --- /dev/null +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/reposiotory/GitHubServiceRepositoryImpl.kt @@ -0,0 +1,49 @@ +package jp.co.yumemi.android.code_check.features.github.reposiotory + +import jp.co.yumemi.android.code_check.core.entity.RepositoryItem +import jp.co.yumemi.android.code_check.features.github.api.GitHubServiceApi +import jp.co.yumemi.android.code_check.features.github.utils.GitHubError +import jp.co.yumemi.android.code_check.features.github.utils.NetworkResult +import org.json.JSONException +import retrofit2.HttpException +import java.io.IOException +import javax.inject.Inject + +class GitHubServiceRepositoryImpl + @Inject + constructor( + private val gitHubRepositoryApi: GitHubServiceApi, + ) : GitHubServiceRepository { + override suspend fun fetchSearchResults(inputText: String): NetworkResult> { + return try { + val repositoryList = gitHubRepositoryApi.getRepository(inputText) + val items = + repositoryList.items.map { item -> + RepositoryItem( + name = item.name, + ownerIconUrl = item.owner.avatarUrl, + language = item.language ?: "none", + stargazersCount = item.stargazersCount, + watchersCount = item.watchersCount, + forksCount = item.forksCount, + openIssuesCount = item.openIssuesCount, + ) + } + NetworkResult.Success(items) + } catch (e: HttpException) { + val error = + when (e.code()) { + 429 -> GitHubError.RateLimitError + 401 -> GitHubError.AuthenticationError + else -> GitHubError.ApiError(e.code(), e.message()) + } + NetworkResult.Error(error) + } catch (e: JSONException) { + NetworkResult.Error(GitHubError.ParseError(e)) + } catch (e: IOException) { + NetworkResult.Error(GitHubError.NetworkError(e)) + } + } + } + +class NetworkException(message: String, cause: Throwable? = null) : Exception(message, cause) diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/reposiotory/NetworkRepository.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/reposiotory/NetworkRepository.kt deleted file mode 100644 index 7014dcc..0000000 --- a/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/reposiotory/NetworkRepository.kt +++ /dev/null @@ -1,59 +0,0 @@ -package jp.co.yumemi.android.code_check.features.github.reposiotory - -import android.content.Context -import android.net.ConnectivityManager -import android.net.NetworkCapabilities -import jp.co.yumemi.android.code_check.RepositoryItem -import jp.co.yumemi.android.code_check.features.github.api.GitHubRepositoryApi -import jp.co.yumemi.android.code_check.features.github.api.GitHubRepositoryApiImpl -import org.json.JSONException - -sealed class NetworkResult { - data class Success(val data: T) : NetworkResult() - - data class Error(val exception: NetworkException) : NetworkResult() -} - -class NetworkRepository( - private val gitHubRepositoryApi: GitHubRepositoryApi = GitHubRepositoryApiImpl(), -) { - suspend fun fetchSearchResults( - inputText: String, - context: Context, - ): NetworkResult> { - if (!isNetworkAvailable(context)) { - return NetworkResult.Error(NetworkException("オフライン状態です")) - } - - return try { - val repositoryList = gitHubRepositoryApi.getRepository(inputText) - val items = - repositoryList.items.map { item -> - RepositoryItem( - name = item.name, - ownerIconUrl = item.owner.avatarUrl, - language = item.language ?: "none", - stargazersCount = item.stargazersCount, - watchersCount = item.watchersCount, - forksCount = item.forksCount, - openIssuesCount = item.openIssuesCount, - ) - } - NetworkResult.Success(items) - } catch (e: JSONException) { - NetworkResult.Error(NetworkException("JSONパースエラー", e)) - } catch (e: Exception) { - NetworkResult.Error(NetworkException("ネットワークエラー", e)) - } - } - - private fun isNetworkAvailable(context: Context): Boolean { - val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - - val network = connectivityManager.activeNetwork ?: return false - val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false - return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - } -} - -class NetworkException(message: String, cause: Throwable? = null) : Exception(message, cause) diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/usecase/GitHubServiceUsecase.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/usecase/GitHubServiceUsecase.kt new file mode 100644 index 0000000..3583218 --- /dev/null +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/usecase/GitHubServiceUsecase.kt @@ -0,0 +1,8 @@ +package jp.co.yumemi.android.code_check.features.github.usecase + +import jp.co.yumemi.android.code_check.core.entity.RepositoryItem +import jp.co.yumemi.android.code_check.features.github.utils.NetworkResult + +interface GitHubServiceUsecase { + suspend fun fetchSearchResults(inputText: String): NetworkResult> +} diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/usecase/GitHubServiceUsecaseImpl.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/usecase/GitHubServiceUsecaseImpl.kt new file mode 100644 index 0000000..a2e4ba6 --- /dev/null +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/usecase/GitHubServiceUsecaseImpl.kt @@ -0,0 +1,22 @@ +package jp.co.yumemi.android.code_check.features.github.usecase + +import jp.co.yumemi.android.code_check.core.api.NetworkConnectivityService +import jp.co.yumemi.android.code_check.core.entity.RepositoryItem +import jp.co.yumemi.android.code_check.features.github.reposiotory.GitHubServiceRepository +import jp.co.yumemi.android.code_check.features.github.reposiotory.NetworkException +import jp.co.yumemi.android.code_check.features.github.utils.NetworkResult +import javax.inject.Inject + +class GitHubServiceUsecaseImpl + @Inject + constructor( + private val repository: GitHubServiceRepository, + private val networkConnectivityService: NetworkConnectivityService, + ) : GitHubServiceUsecase { + override suspend fun fetchSearchResults(inputText: String): NetworkResult> { + if (!networkConnectivityService.isNetworkAvailable()) { + throw NetworkException("オフライン状態です") + } + return repository.fetchSearchResults(inputText) + } + } diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/utils/GitHubError.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/utils/GitHubError.kt new file mode 100644 index 0000000..622fdd7 --- /dev/null +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/utils/GitHubError.kt @@ -0,0 +1,13 @@ +package jp.co.yumemi.android.code_check.features.github.utils + +sealed class GitHubError { + data class NetworkError(val exception: Exception) : GitHubError() + + data class ApiError(val code: Int, val message: String) : GitHubError() + + data class ParseError(val exception: Exception) : GitHubError() + + data object RateLimitError : GitHubError() + + data object AuthenticationError : GitHubError() +} diff --git a/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/utils/NetworkResult.kt b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/utils/NetworkResult.kt new file mode 100644 index 0000000..05d20a3 --- /dev/null +++ b/app/src/main/kotlin/jp/co/yumemi/android/code_check/features/github/utils/NetworkResult.kt @@ -0,0 +1,7 @@ +package jp.co.yumemi.android.code_check.features.github.utils + +sealed class NetworkResult { + data class Success(val data: T) : NetworkResult() + + data class Error(val exception: GitHubError) : NetworkResult() +} diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 01e13df..ea34d7b 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -7,7 +7,7 @@ + app:argType="jp.co.yumemi.android.code_check.core.entity.RepositoryItem" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b820fba..a665d57 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,4 +6,10 @@ "%1$d watchers" "%1$d forks" "%1$d open issues" + 認証エラー + ネットワークエラー + パースエラー + セッションの時間切れ + APIのエラー + フォームが空欄になっています \ No newline at end of file diff --git a/build.gradle b/build.gradle index 27c3380..67879fa 100644 --- a/build.gradle +++ b/build.gradle @@ -6,8 +6,9 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:8.7.3' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.8.5" + classpath 'com.google.dagger:hilt-android-gradle-plugin:2.55' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files