Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'io.michaelrocks.paranoid'
id 'kotlin-kapt'
}

android {
Expand Down Expand Up @@ -87,4 +88,7 @@ dependencies {
testImplementation 'app.cash.turbine:turbine:0.8.0'
testImplementation "com.google.truth:truth:1.1.3"
testImplementation 'org.robolectric:robolectric:4.8'

implementation 'com.google.dagger:dagger:2.42'
kapt 'com.google.dagger:dagger-compiler:2.42'
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,11 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.example.githubuserfinder.app.navigation.NavArgs
import com.example.githubuserfinder.app.navigation.NavigationDestination
import com.example.githubuserfinder.core.data.NetworkRequester
import com.example.githubuserfinder.user_detail.data.adapter.GithubUserDetailJsonAdapter
import com.example.githubuserfinder.user_detail.data.datasource.UsersRemoteDataSource
import com.example.githubuserfinder.user_detail.data.datasource.UsersRemoteDataSourceImpl
import com.example.githubuserfinder.core.di.daggerViewModel
import com.example.githubuserfinder.user_detail.di.DaggerUserDetailComponent
import com.example.githubuserfinder.user_detail.presentation.screen.UserDetailScreen
import com.example.githubuserfinder.user_detail.presentation.viewmodel.UserDetailViewModel
import com.example.githubuserfinder.user_finder.data.adapter.GithubSearchItemJsonAdapter
import com.example.githubuserfinder.user_finder.data.adapter.GithubSearchResponseJsonAdapter
import com.example.githubuserfinder.user_finder.data.datasource.SearchRemoteDataSource
import com.example.githubuserfinder.user_finder.data.datasource.SearchRemoteDataSourceImpl
import com.example.githubuserfinder.user_finder.di.DaggerUserFinderComponent
import com.example.githubuserfinder.user_finder.presentation.screen.UserFinderScreen
import com.example.githubuserfinder.user_finder.presentation.viewmodel.UserFinderViewModel

// As this is a small application, a dependency injection framework isn't used.
// Dependencies are created in the root of the UI manually and injected to different features.
Expand All @@ -28,29 +21,6 @@ fun GithubUserFinderApp() {

val navController = rememberNavController()

// Dependencies
val networkRequester = NetworkRequester()
val githubSearchItemJsonAdapter = GithubSearchItemJsonAdapter()
val githubSearchResponseJsonAdapter =
GithubSearchResponseJsonAdapter(githubSearchItemJsonAdapter = githubSearchItemJsonAdapter)
val githubUserDetailJsonAdapter = GithubUserDetailJsonAdapter()
val searchRemoteDataSource: SearchRemoteDataSource =
SearchRemoteDataSourceImpl(
networkRequester = networkRequester,
githubSearchResponseJsonAdapter = githubSearchResponseJsonAdapter,
)
val usersRemoteDataSource: UsersRemoteDataSource =
UsersRemoteDataSourceImpl(
networkRequester = networkRequester,
githubUserDetailJsonAdapter = githubUserDetailJsonAdapter,
)
val userFinderViewModel = UserFinderViewModel(
searchRemoteDataSource = searchRemoteDataSource,
)
val userDetailViewModel = UserDetailViewModel(
usersRemoteDataSource = usersRemoteDataSource,
)

/**
* This callback must be passed to [UserFinderScreen] so that
* it would be able to navigate to the detail screen
Expand All @@ -76,6 +46,11 @@ fun GithubUserFinderApp() {
composable(
route = NavigationDestination.UserFinderNavigationDestination.routeTemplate,
) {
val userFinderComponent = DaggerUserFinderComponent.builder().build()
val userFinderViewModel = daggerViewModel {
userFinderComponent.getViewModel()
}

UserFinderScreen(
viewModel = userFinderViewModel,
onNavigateToUserDetail = onNavigateToUserDetail,
Expand All @@ -91,6 +66,11 @@ fun GithubUserFinderApp() {
}
),
) { entry ->
val userDetailComponent = DaggerUserDetailComponent.builder().build()
val userDetailViewModel = daggerViewModel {
userDetailComponent.getViewModel()
}

val username = entry.arguments?.getString(NavArgs.Username)
UserDetailScreen(
viewModel = userDetailViewModel,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.githubuserfinder.app.di

import dagger.Component

@Component
interface AppComponent
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.example.githubuserfinder.core.data

import android.util.Log
import com.example.githubuserfinder.BuildConfig
import com.example.githubuserfinder.core.presentation.CoreString
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
Expand All @@ -15,6 +13,7 @@ import java.io.InputStream
import java.net.MalformedURLException
import java.net.URL
import java.nio.charset.StandardCharsets
import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection

/**
Expand All @@ -23,7 +22,7 @@ import javax.net.ssl.HttpsURLConnection
* - Handles exceptions
* - Returns a [DataResult] of type [JSONObject]
* */
class NetworkRequester {
class NetworkRequester @Inject constructor() {

suspend fun invoke(
url: URL,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.example.githubuserfinder.core.di

import androidx.compose.runtime.Composable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
inline fun <reified T : ViewModel> daggerViewModel(
key: String? = null,
crossinline viewModelInstanceCreator: () -> T
): T {
return viewModel(
modelClass = T::class.java,
key = key,
factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return viewModelInstanceCreator() as T
}
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import com.example.githubuserfinder.core.data.getStringNullable
import com.example.githubuserfinder.user_detail.data.datasource.UsersRemoteDataSource
import com.example.githubuserfinder.user_detail.data.model.GithubUserDetail
import com.example.githubuserfinder.user_detail.presentation.UserDetailString
import javax.inject.Inject
import org.json.JSONObject

/**
* This class is used in [UsersRemoteDataSource] and handles
* converting a [JSONObject] to a [GithubUserDetail] model.
* */
class GithubUserDetailJsonAdapter : JsonAdapter<GithubUserDetail> {
class GithubUserDetailJsonAdapter @Inject constructor() : JsonAdapter<GithubUserDetail> {
override fun createEntityFromJson(json: JSONObject): GithubUserDetail {
return GithubUserDetail(
login = json.getString(UserDetailString.UserDetailLoginKey),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import com.example.githubuserfinder.user_detail.data.adapter.GithubUserDetailJso
import com.example.githubuserfinder.user_detail.data.model.GithubUserDetail
import com.example.githubuserfinder.user_detail.presentation.UserDetailString
import java.net.URL
import javax.inject.Inject

class UsersRemoteDataSourceImpl(
class UsersRemoteDataSourceImpl @Inject constructor(
private val networkRequester: NetworkRequester,
private val githubUserDetailJsonAdapter: GithubUserDetailJsonAdapter,
) : UsersRemoteDataSource {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.githubuserfinder.user_detail.di

import com.example.githubuserfinder.user_detail.presentation.viewmodel.UserDetailViewModel
import dagger.Component

@UserDetailScope
@Component(modules = [UserDetailModule::class])
interface UserDetailComponent {
@Component.Builder
interface Builder {
fun build(): UserDetailComponent
}

fun getViewModel(): UserDetailViewModel
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.example.githubuserfinder.user_detail.di

import com.example.githubuserfinder.user_detail.data.datasource.UsersRemoteDataSource
import com.example.githubuserfinder.user_detail.data.datasource.UsersRemoteDataSourceImpl
import dagger.Binds
import dagger.Module

@Module
abstract class UserDetailModule {

@UserDetailScope
@Binds
abstract fun bindUserRemoteDataSource(usersRemoteDataSourceImpl: UsersRemoteDataSourceImpl): UsersRemoteDataSource
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.githubuserfinder.user_detail.di

import javax.inject.Scope

@Scope
@Retention
annotation class UserDetailScope
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.example.githubuserfinder.core.data.DataResult
import com.example.githubuserfinder.user_detail.data.datasource.UsersRemoteDataSource
import com.example.githubuserfinder.user_detail.data.model.GithubUserDetail
import com.example.githubuserfinder.user_detail.presentation.screen.UserDetailScreen
import javax.inject.Inject
import kotlinx.coroutines.Job
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.MutableStateFlow
Expand All @@ -23,7 +24,7 @@ data class UserDetailUiState(
/**
* This class contains all of the [UserDetailScreen] functionalities
* */
class UserDetailViewModel(
class UserDetailViewModel @Inject constructor(
private val usersRemoteDataSource: UsersRemoteDataSource
) : ViewModel() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package com.example.githubuserfinder.user_finder.data.adapter
import com.example.githubuserfinder.core.data.JsonAdapter
import com.example.githubuserfinder.user_finder.data.model.GithubSearchItemModel
import com.example.githubuserfinder.user_finder.presentation.UserFinderString
import javax.inject.Inject
import org.json.JSONObject

/**
* This class will handle converting a [JSONObject] to a [GithubSearchItemModel] model
* */
class GithubSearchItemJsonAdapter : JsonAdapter<GithubSearchItemModel> {
class GithubSearchItemJsonAdapter @Inject constructor() : JsonAdapter<GithubSearchItemModel> {
override fun createEntityFromJson(json: JSONObject): GithubSearchItemModel {
return GithubSearchItemModel(
id = json.getInt(UserFinderString.GithubSearchItemIdKey),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package com.example.githubuserfinder.user_finder.data.adapter
import com.example.githubuserfinder.core.data.JsonAdapter
import com.example.githubuserfinder.user_finder.data.model.GithubSearchItemModel
import com.example.githubuserfinder.user_finder.data.model.GithubSearchResponseModel
import javax.inject.Inject
import org.json.JSONObject

/**
* This class will handle converting a [JSONObject] to a [GithubSearchResponseModel] model
* */
class GithubSearchResponseJsonAdapter(
class GithubSearchResponseJsonAdapter @Inject constructor(
private val githubSearchItemJsonAdapter: GithubSearchItemJsonAdapter,
) : JsonAdapter<GithubSearchResponseModel> {
override fun createEntityFromJson(json: JSONObject): GithubSearchResponseModel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import com.example.githubuserfinder.user_finder.data.adapter.GithubSearchRespons
import com.example.githubuserfinder.user_finder.data.model.GithubSearchResponseModel
import com.example.githubuserfinder.user_finder.presentation.UserFinderString
import java.net.URL
import javax.inject.Inject

class SearchRemoteDataSourceImpl(
class SearchRemoteDataSourceImpl @Inject constructor(
private val networkRequester: NetworkRequester,
private val githubSearchResponseJsonAdapter: GithubSearchResponseJsonAdapter,
) : SearchRemoteDataSource {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.githubuserfinder.user_finder.di

import com.example.githubuserfinder.user_finder.presentation.viewmodel.UserFinderViewModel
import dagger.Component

@UserFinderScope
@Component(modules = [UserFinderModule::class])
interface UserFinderComponent {
@Component.Builder
interface Builder {
fun build(): UserFinderComponent
}

fun getViewModel(): UserFinderViewModel
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.example.githubuserfinder.user_finder.di

import com.example.githubuserfinder.user_finder.data.datasource.SearchRemoteDataSource
import com.example.githubuserfinder.user_finder.data.datasource.SearchRemoteDataSourceImpl
import dagger.Binds
import dagger.Module

@Module
abstract class UserFinderModule {

@UserFinderScope
@Binds
abstract fun bindSearchRemoteDataSource(searchRemoteDataSourceImpl: SearchRemoteDataSourceImpl): SearchRemoteDataSource
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.githubuserfinder.user_finder.di

import javax.inject.Scope

@Scope
@Retention
annotation class UserFinderScope
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.example.githubuserfinder.core.data.DataResult
import com.example.githubuserfinder.user_finder.data.datasource.SearchRemoteDataSource
import com.example.githubuserfinder.user_finder.data.model.GithubSearchResponseModel
import com.example.githubuserfinder.user_finder.presentation.screen.UserFinderScreen
import javax.inject.Inject
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive
Expand All @@ -28,7 +29,7 @@ data class UserFinderUiState(
/**
* This class contains all of the [UserFinderScreen] functionalities
* */
class UserFinderViewModel(
class UserFinderViewModel @Inject constructor(
private val searchRemoteDataSource: SearchRemoteDataSource,
) : ViewModel() {

Expand Down