Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Koin Dependency Injection #9

Merged
merged 12 commits into from Mar 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 9 additions & 6 deletions app/build.gradle
Expand Up @@ -39,7 +39,6 @@ android {
}

dependencies {
def nav_version = '2.5.0-alpha03'

// UI
implementation 'androidx.core:core-ktx:1.7.0'
Expand All @@ -49,22 +48,26 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$nav_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$android_lifecycle_version"

// Processing
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-native-mt'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0-native-mt'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"

// IO Service
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.github.bumptech.glide:glide:4.13.1'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.5'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.6'

// DI
implementation "io.insert-koin:koin-core:$koin_version"
implementation "io.insert-koin:koin-android:$koin_version"

// Test
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation "androidx.arch.core:core-testing:2.1.0"
androidTestImplementation "com.squareup.okhttp3:mockwebserver:4.1.0"
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:5.0.0-alpha.6'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Expand Up @@ -8,6 +8,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

<application
android:name=".ApplicationClass"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
Expand Down
@@ -0,0 +1,26 @@
package com.github.husseinhj.githubuser

import android.app.Application
import org.koin.core.context.startKoin
import org.koin.android.ext.koin.androidLogger
import org.koin.android.ext.koin.androidContext
import com.github.husseinhj.githubuser.modules.*

class ApplicationClass : Application() {

override fun onCreate() {
super.onCreate()

startKoin {
androidLogger()
androidContext(this@ApplicationClass)
modules(listOf(
applicationModule,
repositoryModules,
viewModelModules,
webServiceModules,
retrofitModule
))
}
}
}
@@ -1,11 +1,11 @@
package com.github.husseinhj.githubuser.adapters

import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.content.Context
import android.widget.BaseAdapter
import android.view.LayoutInflater
import android.annotation.SuppressLint
import com.github.husseinhj.githubuser.R
import com.github.husseinhj.githubuser.models.UserSimpleDetailsModel
import com.github.husseinhj.githubuser.views.viewholders.UserSearchResultViewHolder
Expand Down
@@ -0,0 +1,26 @@
package com.github.husseinhj.githubuser.bases

import android.view.Menu
import com.github.husseinhj.githubuser.R
import org.koin.android.ext.android.inject
import org.koin.core.parameter.parametersOf
import androidx.appcompat.app.AppCompatActivity
import com.github.husseinhj.githubuser.utils.ToolbarAppearance

open class BaseAppCompatActivity: AppCompatActivity() {
private val toolbarAppearance: ToolbarAppearance by inject {
parametersOf(this, R.menu.main_menu)
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
toolbarAppearance.configureSearchBarByMenu(menu)

return super.onCreateOptionsMenu(menu)
}

override fun onBackPressed() {
if (!toolbarAppearance.collapseSearchBar()) {
super.onBackPressed()
}
}
}
@@ -1,47 +1,37 @@
package com.github.husseinhj.githubuser.bases

import android.os.Bundle
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.fragment.app.Fragment
import com.github.husseinhj.githubuser.R
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.SavedStateViewModelFactory
import org.koin.android.ext.android.inject
import org.koin.core.parameter.parametersOf
import com.github.husseinhj.githubuser.extensions.navigate
import com.github.husseinhj.githubuser.utils.ToolbarAppearance
import com.github.husseinhj.githubuser.utils.NavigationAppearance
import com.github.husseinhj.githubuser.utils.OnTextChangedListener
import com.github.husseinhj.githubuser.utils.OnFocusChangedListener
import com.github.husseinhj.githubuser.views.activities.MainActivity

open class BaseFragment: Fragment() {
private var title: String? = null
private var toolbarAppearance: ToolbarAppearance? = null
private val toolbarAppearance: ToolbarAppearance by inject {
parametersOf(this.activity, R.menu.main_menu)
}
private var searchBarTextChanged: OnTextChangedListener? = null
private var searchBarFocusListener: OnFocusChangedListener? = null

override fun onAttach(context: Context) {
super.onAttach(context)
toolbarAppearance = (this.requireActivity() as? MainActivity)?.toolbarAppearance
private val navigationAppearance: NavigationAppearance by inject {
parametersOf(this.activity)
}
private var searchBarFocusListener: OnFocusChangedListener? = null

override fun onStart() {
super.onStart()

toolbarAppearance?.setTitle(title)

toolbarAppearance?.setOnFocusListener {
toolbarAppearance.setOnFocusListener {
this.searchBarFocusListener?.invoke(it)
}
toolbarAppearance?.setOnTextChangedListener {
toolbarAppearance.setOnTextChangedListener {
this.searchBarTextChanged?.invoke(it)
}
}

internal fun <T : ViewModel> getSavedStateViewModel(modelClass: Class<T>): T {
val savedStateVM = SavedStateViewModelFactory(null, this)
return ViewModelProvider(this, savedStateVM)[modelClass]
}

fun setOnSearchBarFocusListener(listener: OnFocusChangedListener) {
this.searchBarFocusListener = listener
}
Expand All @@ -52,43 +42,32 @@ open class BaseFragment: Fragment() {

fun navigateFromHomeToSearchFragment() {
try {
this.navigate(
to = R.id.action_homeFragment_to_searchUserFragment,
title = getString(R.string.user_search_title),
showSoftBackButton = true
)
this.navigate(to = R.id.action_homeFragment_to_searchUserFragment)
} catch (e: Exception) {
println("Could not navigate for ${e.message}")
}
toolbarAppearance?.expandSearchBar()
toolbarAppearance?.focusOnSearchBar()
toolbarAppearance.expandSearchBar()
toolbarAppearance.focusOnSearchBar()
}

fun navigateFromDetailToSearchFragment() {
this.navigate(
to = R.id.action_userDetailFragment_to_searchUserFragment,
title = getString(R.string.user_search_title),
showSoftBackButton = true
)
this.navigate(to = R.id.action_userDetailFragment_to_searchUserFragment)
}

fun navigateFromSearchToDetailFragment(data: Bundle) {
toolbarAppearance?.collapseSearchBar()
toolbarAppearance?.clearFocusOnSearchBar()
toolbarAppearance.collapseSearchBar()
toolbarAppearance.clearFocusOnSearchBar()

this.navigate(
data = data,
to = R.id.action_searchUserFragment_to_userDetailFragment,
title = getString(R.string.user_profile_title),
showSoftBackButton = true
)
to = R.id.action_searchUserFragment_to_userDetailFragment)
}

fun setTitle(pageTitle: String) {
this.title = pageTitle
navigationAppearance.setTitle(pageTitle)
}

fun enableBackButton(enable: Boolean) {
toolbarAppearance?.setShowBackSoftButton(enable)
navigationAppearance.setShowBackSoftButton(enable)
}
}
@@ -1,5 +1,8 @@
package com.github.husseinhj.githubuser.consts

const val READ_TIMEOUT = 30L
const val WRITE_TIMEOUT = 30L
const val CONNECT_TIMEOUT = 30L
const val DEBOUNCE_FOR_SEARCH: Long = 300L
const val GITHUB_USERNAME: String = "github_username"
const val GITHUB_API_BASE_URL: String = "https://api.github.com/"
Expand Down
Expand Up @@ -3,6 +3,8 @@ package com.github.husseinhj.githubuser.extensions
import androidx.appcompat.app.AppCompatActivity

fun AppCompatActivity.showSoftBackButton(show: Boolean) {
this.supportActionBar?.setDisplayHomeAsUpEnabled(show)
this.supportActionBar?.setDisplayShowHomeEnabled(show)
this.supportActionBar?.apply {
setDisplayHomeAsUpEnabled(show)
setDisplayShowHomeEnabled(show)
}
}
Expand Up @@ -2,9 +2,7 @@ package com.github.husseinhj.githubuser.extensions

import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.fragment.NavHostFragment
import com.github.husseinhj.githubuser.views.activities.MainActivity

fun Fragment.navigate(to: Int, data: Bundle? = null) {
NavHostFragment.findNavController(this)
Expand All @@ -14,21 +12,4 @@ fun Fragment.navigate(to: Int, data: Bundle? = null) {
fun Fragment.navigateUp() {
NavHostFragment.findNavController(this)
.navigateUp()
}

fun Fragment.navigate(to: Int, data: Bundle? = null, title: String? = null, showSoftBackButton: Boolean? = null) {
NavHostFragment.findNavController(this)
.navigate(to, data)

val toolbarAppearance = (this.activity as? MainActivity)?.toolbarAppearance
toolbarAppearance?.setTitle(title)
toolbarAppearance?.setShowBackSoftButton(showSoftBackButton ?: false)
}

fun Fragment.showSoftBackButton(show: Boolean) {
this.getAppCompatActivity().showSoftBackButton(show)
}

fun Fragment.getAppCompatActivity(): AppCompatActivity {
return this.requireActivity() as AppCompatActivity
}
@@ -0,0 +1,17 @@
package com.github.husseinhj.githubuser.modules

import org.koin.dsl.module
import com.github.husseinhj.githubuser.utils.ToolbarAppearance
import com.github.husseinhj.githubuser.utils.NavigationAppearance

val applicationModule = module {
// Toolbar
single {
ToolbarAppearance(get(), get())
}

// Navigation
factory {
NavigationAppearance(get())
}
}
@@ -0,0 +1,15 @@
package com.github.husseinhj.githubuser.modules

import org.koin.dsl.module
import com.github.husseinhj.githubuser.services.repositories.UserRepository
import com.github.husseinhj.githubuser.services.repositories.SearchRepository

val repositoryModules = module {
single {
UserRepository(get())
}

single {
SearchRepository(get())
}
}
@@ -0,0 +1,14 @@
package com.github.husseinhj.githubuser.modules

import org.koin.dsl.module
import com.google.gson.GsonBuilder
import com.github.husseinhj.githubuser.services.providers.getRetrofit
import com.github.husseinhj.githubuser.services.providers.retrofitHttpClient
import com.github.husseinhj.githubuser.services.interceptors.AuthenticationInterceptor

val retrofitModule = module {
single { getRetrofit() }
single { retrofitHttpClient() }
single { GsonBuilder().create() }
single { AuthenticationInterceptor() }
}
@@ -0,0 +1,30 @@
package com.github.husseinhj.githubuser.modules

import org.koin.dsl.module
import org.koin.androidx.viewmodel.dsl.viewModel
import com.github.husseinhj.githubuser.viewmodels.fragments.HomeViewModel
import com.github.husseinhj.githubuser.viewmodels.fragments.SearchUserViewModel
import com.github.husseinhj.githubuser.viewmodels.fragments.UserDetailViewModel
import com.github.husseinhj.githubuser.viewmodels.activities.MainActivityViewModel

val viewModelModules = module {
// Main Activity ViewModel
viewModel {
MainActivityViewModel()
}

// On-Boarding ViewModel
viewModel {
HomeViewModel()
}

// Search User ViewModel
viewModel {
SearchUserViewModel(get(), get())
}

// User Profile ViewModel
viewModel {
UserDetailViewModel(get(), get())
}
}
@@ -0,0 +1,15 @@
package com.github.husseinhj.githubuser.modules

import com.github.husseinhj.githubuser.services.repositories.SearchRepository
import com.github.husseinhj.githubuser.services.repositories.UserRepository
import retrofit2.Retrofit
import org.koin.dsl.module
import com.github.husseinhj.githubuser.services.repositories.interfaces.IUserRepository
import com.github.husseinhj.githubuser.services.repositories.interfaces.ISearchRepository

val webServiceModules = module {
single(createdAtStart = false) { get<Retrofit>().create(IUserRepository::class.java) }
single(createdAtStart = false) { get<Retrofit>().create(ISearchRepository::class.java) }
single { UserRepository(get()) }
single { SearchRepository(get()) }
}