├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── github │ │ └── harmittaa │ │ └── koinexample │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── harmittaa │ │ │ └── koinexample │ │ │ ├── activity │ │ │ └── MainActivity.kt │ │ │ ├── application │ │ │ └── KoinExampleApplication.kt │ │ │ ├── fragment │ │ │ ├── WeatherFragment.kt │ │ │ └── WeatherViewModel.kt │ │ │ ├── model │ │ │ └── Weather.kt │ │ │ ├── networking │ │ │ ├── AuthInterceptor.kt │ │ │ ├── Resource.kt │ │ │ ├── ResponseHandler.kt │ │ │ ├── RetrofitClient.kt │ │ │ ├── Status.kt │ │ │ └── WeatherApi.kt │ │ │ ├── persistence │ │ │ └── Preferences.kt │ │ │ └── repository │ │ │ └── WeatherRepository.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── fragment_view.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── github │ └── harmittaa │ └── koinexample │ ├── fragment │ └── WeatherViewModelTest.kt │ ├── networking │ └── ResponseHandlerTest.kt │ └── repository │ └── WeatherRepositoryTest.kt ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | *.aab 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | release/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Android Studio Navigation editor temp files 32 | .navigation/ 33 | 34 | # Android Studio captures folder 35 | captures/ 36 | 37 | # IntelliJ 38 | *.iml 39 | .idea/ 40 | .idea/workspace.xml 41 | .idea/tasks.xml 42 | .idea/gradle.xml 43 | .idea/assetWizardSettings.xml 44 | .idea/dictionaries 45 | .idea/libraries 46 | # Android Studio 3 in .gitignore file. 47 | .idea/caches 48 | .idea/modules.xml 49 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 50 | .idea/navEditor.xml 51 | 52 | # Keystore files 53 | # Uncomment the following lines if you do not want to check your keystore files in. 54 | #*.jks 55 | #*.keystore 56 | 57 | # External native build folder generated in Android Studio 2.2 and later 58 | .externalNativeBuild 59 | 60 | # Google Services (e.g. APIs or Firebase) 61 | # google-services.json 62 | 63 | # Freeline 64 | freeline.py 65 | freeline/ 66 | freeline_project_description.json 67 | 68 | # fastlane 69 | fastlane/report.xml 70 | fastlane/Preview.html 71 | fastlane/screenshots 72 | fastlane/test_output 73 | fastlane/readme.md 74 | 75 | # Version control 76 | vcs.xml 77 | 78 | # lint 79 | lint/intermediates/ 80 | lint/generated/ 81 | lint/outputs/ 82 | lint/tmp/ 83 | # lint/reports/ 84 | 85 | # General 86 | .DS_Store 87 | .AppleDouble 88 | .LSOverride 89 | 90 | # Icon must end with two \r 91 | Icon 92 | 93 | # Thumbnails 94 | ._* 95 | 96 | # Files that might appear in the root of a volume 97 | .DocumentRevisions-V100 98 | .fseventsd 99 | .Spotlight-V100 100 | .TemporaryItems 101 | .Trashes 102 | .VolumeIcon.icns 103 | .com.apple.timemachine.donotpresent 104 | 105 | # Directories potentially created on remote AFP share 106 | .AppleDB 107 | .AppleDesktop 108 | Network Trash Folder 109 | Temporary Items 110 | .apdisk -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KoinExample 2 | 3 | This is a basic example for setting up Koin with Retrofit 2.6.0. This example also includes error handling and testing. 4 | 5 | I've written a 4 part Medium series to go with the example code. 6 | 7 | This set of examples builds up to many parts, check them out too: 8 | * [Part 1. Setting up Koin 2.0.1 for Android](https://medium.com/@harmittaa/setting-up-koin-2-0-1-for-android-ebf11de01816) -- [branch: setting-up](https://github.com/harmittaa/KoinExample/tree/setting-up) 9 | * [Part 2. Retrofit 2.6.0 with Koin and coroutines](https://medium.com/@harmittaa/retrofit-2-6-0-with-koin-and-coroutines-4ff45a4792fc) -- [branch: setting-up](https://github.com/harmittaa/KoinExample/tree/setting-up) 10 | * [Part 3. Retrofit 2.6.0 with Koin and coroutines: network error handling](https://medium.com/@harmittaa/retrofit-2-6-0-with-koin-and-coroutines-network-error-handling-a5b98b5e5ca0) -- [branch: error-handling](https://github.com/harmittaa/KoinExample/tree/error-handling) 11 | * [Part 4. Retrofit 2.6.0 with Koin and coroutines: testing the ViewModel and Repository](https://medium.com/@harmittaa/retrofit-2-6-0-with-koin-and-coroutines-testing-your-layers-42d2a71566f1) -- [branch: testing]( https://github.com/harmittaa/KoinExample/tree/testing) 12 | 13 | 14 | ## Setting up 15 | 16 | Clone and add your OpenweatherMaps API key to gradle.properties. 17 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion 29 8 | defaultConfig { 9 | applicationId "com.github.harmittaa.koinexample" 10 | minSdkVersion 21 11 | targetSdkVersion 29 12 | versionCode 1 13 | versionName "1.0" 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | dataBinding { 16 | enabled = true 17 | } 18 | } 19 | 20 | compileOptions { 21 | sourceCompatibility = 1.8 22 | targetCompatibility = 1.8 23 | } 24 | 25 | kotlinOptions { 26 | jvmTarget = "1.8" 27 | } 28 | 29 | buildTypes { 30 | debug { 31 | try { 32 | minifyEnabled false 33 | buildConfigField "String", "API_URL", "\"https://api.openweathermap.org/data/2.5/\"" 34 | buildConfigField "String", "API_KEY", openweathermap_key 35 | debuggable true 36 | } catch (e) { 37 | // add gradle.properties file with the following property: 38 | // openweathermap_key = "YOUR_API_KEY" 39 | throw e 40 | } 41 | } 42 | release { 43 | minifyEnabled false 44 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 45 | } 46 | } 47 | } 48 | 49 | dependencies { 50 | implementation fileTree(dir: 'libs', include: ['*.jar']) 51 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 52 | androidTestImplementation 'androidx.test:runner:1.2.0' 53 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 54 | 55 | def lifecycle_version = "2.2.0-rc01" 56 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" 57 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" 58 | 59 | def fragment_version = "1.2.0-beta01" 60 | implementation "androidx.fragment:fragment-ktx:$fragment_version" 61 | 62 | def core_version = "1.1.0" 63 | implementation "androidx.appcompat:appcompat:$core_version" 64 | implementation "androidx.core:core-ktx:$core_version" 65 | 66 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 67 | 68 | kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" 69 | 70 | def koin_version = "2.0.1" 71 | implementation "org.koin:koin-android:$koin_version" 72 | // use org.koin:koin-android-viewmodel instead for non-androidx projects! 73 | implementation "org.koin:koin-androidx-viewmodel:$koin_version" 74 | implementation "org.koin:koin-core:$koin_version" 75 | 76 | def retrofit_version = "2.6.2" 77 | implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" 78 | implementation "com.squareup.retrofit2:retrofit:$retrofit_version" 79 | 80 | def okhttp_version = "4.2.2" 81 | implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version" 82 | 83 | def mockito_version = "3.1.0" 84 | testImplementation "org.mockito:mockito-core:$mockito_version" 85 | testImplementation "org.mockito:mockito-inline:$mockito_version" 86 | testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" 87 | 88 | testImplementation 'junit:junit:4.12' 89 | testImplementation 'android.arch.core:core-testing:2.1.0' 90 | testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.2' 91 | } 92 | -------------------------------------------------------------------------------- /app/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | # ADD YOUR KEY: 23 | #https://openweathermap.org/appid 24 | openweathermap_key="123" -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/harmittaa/koinexample/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.harmittaa.koinexample 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.github.harmittaa.koinexample", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/harmittaa/koinexample/activity/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.harmittaa.koinexample.activity 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import com.github.harmittaa.koinexample.R 6 | import com.github.harmittaa.koinexample.fragment.WeatherFragment 7 | import com.github.harmittaa.koinexample.persistence.Preferences 8 | import org.koin.android.ext.android.inject 9 | 10 | class MainActivity : AppCompatActivity() { 11 | private val preferences: Preferences by inject() 12 | private val exampleFragment: WeatherFragment by inject() 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | setContentView(R.layout.activity_main) 17 | } 18 | 19 | override fun onResume() { 20 | super.onResume() 21 | if (preferences.getShouldShowFragment()) { 22 | supportFragmentManager.beginTransaction().replace(R.id.root, exampleFragment, "weather").commit() 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/harmittaa/koinexample/application/KoinExampleApplication.kt: -------------------------------------------------------------------------------- 1 | package com.github.harmittaa.koinexample.application 2 | 3 | import android.app.Application 4 | import com.github.harmittaa.koinexample.fragment.fragmentModule 5 | import com.github.harmittaa.koinexample.fragment.viewModelModule 6 | import com.github.harmittaa.koinexample.repository.forecastModule 7 | import com.github.harmittaa.koinexample.networking.networkModule 8 | import com.github.harmittaa.koinexample.persistence.prefModule 9 | import org.koin.android.ext.koin.androidContext 10 | import org.koin.android.ext.koin.androidLogger 11 | import org.koin.core.context.startKoin 12 | 13 | class KoinExampleApplication : Application() { 14 | 15 | override fun onCreate() { 16 | super.onCreate() 17 | startKoin { 18 | androidLogger() 19 | androidContext(this@KoinExampleApplication) 20 | modules(listOf(prefModule, fragmentModule, viewModelModule, networkModule, 21 | forecastModule 22 | )) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/harmittaa/koinexample/fragment/WeatherFragment.kt: -------------------------------------------------------------------------------- 1 | package com.github.harmittaa.koinexample.fragment 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.databinding.DataBindingUtil 9 | import androidx.fragment.app.Fragment 10 | import androidx.lifecycle.Observer 11 | import com.github.harmittaa.koinexample.R 12 | import com.github.harmittaa.koinexample.databinding.FragmentViewBinding 13 | import com.github.harmittaa.koinexample.model.TempData 14 | import com.github.harmittaa.koinexample.model.Weather 15 | import com.github.harmittaa.koinexample.networking.Resource 16 | import com.github.harmittaa.koinexample.networking.Status 17 | import org.koin.androidx.viewmodel.ext.android.viewModel 18 | import org.koin.dsl.module 19 | 20 | val fragmentModule = module { 21 | factory { WeatherFragment() } 22 | } 23 | 24 | class WeatherFragment : Fragment() { 25 | private val exampleViewModel: WeatherViewModel by viewModel() 26 | private lateinit var binding: FragmentViewBinding 27 | 28 | private val observer = Observer> { 29 | when (it.status) { 30 | Status.SUCCESS -> updateTemperatureText(it.data!!.name, it.data.temp) 31 | Status.ERROR -> showError(it.message!!) 32 | Status.LOADING -> showLoading() 33 | } 34 | } 35 | 36 | override fun onCreateView( 37 | inflater: LayoutInflater, 38 | container: ViewGroup?, 39 | savedInstanceState: Bundle? 40 | ): View? { 41 | super.onCreateView(inflater, container, savedInstanceState) 42 | binding = DataBindingUtil.inflate(inflater, R.layout.fragment_view, container, false) 43 | binding.viewModel = exampleViewModel 44 | exampleViewModel.weather.observe(this, observer) 45 | return binding.root 46 | } 47 | 48 | @SuppressLint("SetTextI18n") 49 | private fun showLoading() { 50 | binding.weatherInfo.text = "Loading..." 51 | } 52 | 53 | @SuppressLint("SetTextI18n") 54 | private fun showError(message: String) { 55 | binding.weatherInfo.text = "Error: $message" 56 | } 57 | 58 | @SuppressLint("SetTextI18n") 59 | private fun updateTemperatureText(name: String, temp: TempData) { 60 | binding.weatherInfo.text = "Temperature at ${name} is ${temp.temp} celsius" 61 | } 62 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/harmittaa/koinexample/fragment/WeatherViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.github.harmittaa.koinexample.fragment 2 | 3 | import androidx.lifecycle.* 4 | import com.github.harmittaa.koinexample.repository.WeatherRepository 5 | import com.github.harmittaa.koinexample.networking.Resource 6 | import kotlinx.coroutines.Dispatchers 7 | import org.koin.dsl.module 8 | 9 | val viewModelModule = module { 10 | factory { WeatherViewModel(get()) } 11 | } 12 | 13 | class WeatherViewModel( 14 | private val weatherRepo: WeatherRepository 15 | ) : ViewModel() { 16 | 17 | private val location = MutableLiveData() 18 | 19 | fun getWeather(input: String) { 20 | location.value = input 21 | } 22 | 23 | var weather = location.switchMap { location -> 24 | liveData(Dispatchers.IO) { 25 | emit(Resource.loading(null)) 26 | emit(weatherRepo.getWeather(location)) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/harmittaa/koinexample/model/Weather.kt: -------------------------------------------------------------------------------- 1 | package com.github.harmittaa.koinexample.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class Weather( 6 | @SerializedName("main") val temp: TempData, 7 | val name: String 8 | ) 9 | 10 | data class TempData( 11 | val temp: Double, 12 | val humidity: Int 13 | ) 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/harmittaa/koinexample/networking/AuthInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.github.harmittaa.koinexample.networking 2 | 3 | import com.github.harmittaa.koinexample.BuildConfig 4 | import okhttp3.Interceptor 5 | import okhttp3.Response 6 | 7 | class AuthInterceptor() : Interceptor { 8 | override fun intercept(chain: Interceptor.Chain): Response { 9 | var req = chain.request() 10 | // DONT INCLUDE API KEYS IN YOUR SOURCE CODE 11 | // Edit (or add) a gradle.properties file in your project root 12 | // and add the API_KEY there! 13 | val url = req.url.newBuilder().addQueryParameter("APPID", BuildConfig.API_KEY).build() 14 | req = req.newBuilder().url(url).build() 15 | return chain.proceed(req) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/harmittaa/koinexample/networking/Resource.kt: -------------------------------------------------------------------------------- 1 | package com.github.harmittaa.koinexample.networking 2 | 3 | data class Resource(val status: Status, val data: T?, val message: String?) { 4 | companion object { 5 | fun success(data: T?): Resource { 6 | return Resource(Status.SUCCESS, data, null) 7 | } 8 | 9 | fun error(msg: String, data: T?): Resource { 10 | return Resource(Status.ERROR, data, msg) 11 | } 12 | 13 | fun loading(data: T?): Resource { 14 | return Resource(Status.LOADING, data, null) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/harmittaa/koinexample/networking/ResponseHandler.kt: -------------------------------------------------------------------------------- 1 | package com.github.harmittaa.koinexample.networking 2 | 3 | import retrofit2.HttpException 4 | import java.lang.Exception 5 | import java.net.SocketTimeoutException 6 | 7 | enum class ErrorCodes(val code: Int) { 8 | SocketTimeOut(-1) 9 | } 10 | 11 | open class ResponseHandler { 12 | fun handleSuccess(data: T): Resource { 13 | return Resource.success(data) 14 | } 15 | 16 | fun handleException(e: Exception): Resource { 17 | return when (e) { 18 | is HttpException -> Resource.error(getErrorMessage(e.code()), null) 19 | is SocketTimeoutException -> Resource.error(getErrorMessage(ErrorCodes.SocketTimeOut.code), null) 20 | else -> Resource.error(getErrorMessage(Int.MAX_VALUE), null) 21 | } 22 | } 23 | 24 | private fun getErrorMessage(code: Int): String { 25 | return when (code) { 26 | ErrorCodes.SocketTimeOut.code -> "Timeout" 27 | 401 -> "Unauthorised" 28 | 404 -> "Not found" 29 | else -> "Something went wrong" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/harmittaa/koinexample/networking/RetrofitClient.kt: -------------------------------------------------------------------------------- 1 | package com.github.harmittaa.koinexample.networking 2 | 3 | import com.github.harmittaa.koinexample.BuildConfig 4 | import okhttp3.OkHttpClient 5 | import okhttp3.logging.HttpLoggingInterceptor 6 | import org.koin.dsl.module 7 | import retrofit2.Retrofit 8 | import retrofit2.converter.gson.GsonConverterFactory 9 | 10 | val networkModule = module { 11 | factory { AuthInterceptor() } 12 | factory { provideOkHttpClient(get(), get()) } 13 | factory { provideForecastApi(get()) } 14 | factory { provideLoggingInterceptor() } 15 | single { provideRetrofit(get()) } 16 | factory { ResponseHandler() } 17 | } 18 | 19 | fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { 20 | return Retrofit.Builder().baseUrl(BuildConfig.API_URL).client(okHttpClient) 21 | .addConverterFactory(GsonConverterFactory.create()).build() 22 | } 23 | 24 | fun provideOkHttpClient(authInterceptor: AuthInterceptor, loggingInterceptor: HttpLoggingInterceptor): OkHttpClient { 25 | return OkHttpClient().newBuilder().addInterceptor(authInterceptor).addInterceptor(loggingInterceptor).build() 26 | } 27 | 28 | fun provideLoggingInterceptor(): HttpLoggingInterceptor { 29 | val logger = HttpLoggingInterceptor() 30 | logger.level = HttpLoggingInterceptor.Level.BASIC 31 | return logger 32 | } 33 | 34 | fun provideForecastApi(retrofit: Retrofit): WeatherApi = retrofit.create(WeatherApi::class.java) 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/harmittaa/koinexample/networking/Status.kt: -------------------------------------------------------------------------------- 1 | package com.github.harmittaa.koinexample.networking 2 | 3 | enum class Status { 4 | SUCCESS, 5 | ERROR, 6 | LOADING 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/harmittaa/koinexample/networking/WeatherApi.kt: -------------------------------------------------------------------------------- 1 | package com.github.harmittaa.koinexample.networking 2 | 3 | import com.github.harmittaa.koinexample.model.Weather 4 | import retrofit2.HttpException 5 | import retrofit2.Response 6 | import retrofit2.http.GET 7 | import retrofit2.http.Query 8 | 9 | interface WeatherApi { 10 | 11 | @GET("weather") 12 | suspend fun getForecast(@Query("q")location: String, 13 | @Query("units") unit: String): Weather 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/harmittaa/koinexample/persistence/Preferences.kt: -------------------------------------------------------------------------------- 1 | package com.github.harmittaa.koinexample.persistence 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import org.koin.android.ext.koin.androidContext 6 | import org.koin.dsl.module 7 | 8 | val prefModule = module { 9 | single { Preferences(androidContext()) } 10 | } 11 | 12 | class Preferences(context: Context) { 13 | private val preferences: SharedPreferences = context.getSharedPreferences("prefs", Context.MODE_PRIVATE) 14 | private val showFragmentKey = "showFragment" 15 | private val fragmentContentKey = "fragmentContent" 16 | 17 | init { 18 | storeShouldShowFragment(true) 19 | storeFragmentContent("Hey, this is a fragment") 20 | } 21 | 22 | private fun storeFragmentContent(content: String) { 23 | preferences.edit().putString(fragmentContentKey, content).apply() 24 | } 25 | 26 | fun getFragmentContent(): String? { 27 | return preferences.getString(fragmentContentKey, "") 28 | } 29 | 30 | private fun storeShouldShowFragment(shouldShow: Boolean) { 31 | preferences.edit().putBoolean(showFragmentKey, shouldShow).apply() 32 | } 33 | 34 | fun getShouldShowFragment(): Boolean { 35 | return preferences.getBoolean(showFragmentKey, false) 36 | } 37 | 38 | 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/harmittaa/koinexample/repository/WeatherRepository.kt: -------------------------------------------------------------------------------- 1 | package com.github.harmittaa.koinexample.repository 2 | 3 | import com.github.harmittaa.koinexample.model.Weather 4 | import com.github.harmittaa.koinexample.networking.Resource 5 | import com.github.harmittaa.koinexample.networking.ResponseHandler 6 | import com.github.harmittaa.koinexample.networking.WeatherApi 7 | import org.koin.dsl.module 8 | 9 | val forecastModule = module { 10 | factory { WeatherRepository(get(), get()) } 11 | } 12 | 13 | open class WeatherRepository( 14 | private val weatherApi: WeatherApi, 15 | private val responseHandler: ResponseHandler 16 | ) { 17 | 18 | suspend fun getWeather(location: String): Resource { 19 | return try { 20 | val response = weatherApi.getForecast(location, "metric") 21 | return responseHandler.handleSuccess(response) 22 | } catch (e: Exception) { 23 | responseHandler.handleException(e) 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 17 | 18 | 29 | 30 |