Skip to content

Commit

Permalink
Merge pull request #9 from Husseinhj/imp/use_depenency_injection
Browse files Browse the repository at this point in the history
Use Koin Dependency Injection
  • Loading branch information
Husseinhj committed Mar 15, 2022
2 parents 7c3a4a7 + c95aabb commit 2e28966
Show file tree
Hide file tree
Showing 27 changed files with 310 additions and 172 deletions.
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()) }
}

0 comments on commit 2e28966

Please sign in to comment.