Skip to content

Commit 8f2385d

Browse files
author
Petros Paraskevopoulos
committed
[github] feature: Add web service
This is the GitHub specific web service that is responsible to query on demand for "repositories" with a specific name.
1 parent 83f3512 commit 8f2385d

File tree

24 files changed

+276
-16
lines changed

24 files changed

+276
-16
lines changed

config/gradle/dependencies/dependencies.gradle

+11
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ ext.deps = [
4848
'rxandroid': "io.reactivex.rxjava2:rxandroid:$versions.rxandroid",
4949
],
5050

51+
'network' : [
52+
'gson' : "com.google.code.gson:gson:$versions.gson",
53+
'okhttpLogging': "com.squareup.okhttp3:logging-interceptor:$versions.okhttp",
54+
],
55+
56+
'rest' : [
57+
'retrofit' : "com.squareup.retrofit2:retrofit:$versions.retrofit",
58+
'retrofitGson': "com.squareup.retrofit2:converter-gson:$versions.retrofit",
59+
'retrofitRx' : "com.squareup.retrofit2:adapter-rxjava2:$versions.retrofit",
60+
],
61+
5162
'logging' : [
5263
'timber': "com.jakewharton.timber:timber:$versions.timber",
5364
],

config/gradle/dependencies/versioning.gradle

+7
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ ext.versions = [
2626
rxjava : '2.1.16',
2727
rxandroid : '2.0.2',
2828

29+
// Network
30+
gson : "2.8.5",
31+
okhttp : "3.10.0",
32+
33+
// Rest
34+
retrofit : "2.4.0",
35+
2936
// Logging
3037
timber : '4.7.1',
3138

data/build.gradle

+5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ dependencies {
3737
kapt deps.di.daggerAndroidProcessor
3838
implementation deps.rx.rxjava
3939
implementation deps.rx.rxandroid
40+
implementation deps.network.okhttpLogging
41+
implementation deps.network.gson
42+
implementation deps.rest.retrofit
43+
implementation deps.rest.retrofitGson
44+
implementation deps.rest.retrofitRx
4045
implementation deps.logging.timber
4146

4247
testImplementation deps.test.junit
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.petros.github.data
2+
3+
import android.content.Context
4+
import android.support.annotation.IntegerRes
5+
6+
/* CONTEXT */
7+
8+
fun Context.getLong(@IntegerRes id: Int): Long = resources.getInteger(id).toLong()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package io.petros.github.data.di.dagger
2+
3+
import android.content.Context
4+
import com.google.gson.Gson
5+
import dagger.Module
6+
import dagger.Provides
7+
import io.petros.github.data.BuildConfig
8+
import io.petros.github.data.R
9+
import io.petros.github.data.getLong
10+
import io.petros.github.data.network.WebService
11+
import io.petros.github.data.network.interceptor.HeaderInterceptor
12+
import io.petros.github.data.network.rest.RestApi
13+
import io.petros.github.data.network.rest.RestClient
14+
import okhttp3.OkHttpClient
15+
import okhttp3.logging.HttpLoggingInterceptor
16+
import retrofit2.Retrofit
17+
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
18+
import retrofit2.converter.gson.GsonConverterFactory
19+
import java.util.concurrent.TimeUnit
20+
import javax.inject.Singleton
21+
22+
@Module
23+
class NetworkModule {
24+
25+
@Provides
26+
@Singleton
27+
fun providesGson() = Gson()
28+
29+
@Provides
30+
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
31+
val logging = HttpLoggingInterceptor()
32+
logging.level = HttpLoggingInterceptor.Level.BODY
33+
return logging
34+
}
35+
36+
@Provides
37+
@Singleton
38+
fun provideOkHttpClient(
39+
context: Context,
40+
headerInterceptor: HeaderInterceptor,
41+
loggingInterceptor: HttpLoggingInterceptor
42+
): OkHttpClient {
43+
val okHttpBuilder = OkHttpClient.Builder()
44+
.connectTimeout(context.getLong(R.integer.network_timeout), TimeUnit.MILLISECONDS)
45+
okHttpBuilder.addInterceptor(headerInterceptor)
46+
if (BuildConfig.DEBUG) okHttpBuilder.addInterceptor(loggingInterceptor)
47+
return okHttpBuilder.build()
48+
}
49+
50+
@Provides
51+
@Singleton
52+
fun provideRetrofit(context: Context, gson: Gson, httpClient: OkHttpClient): Retrofit {
53+
return Retrofit.Builder()
54+
.baseUrl(context.getString(R.string.rest_github_url))
55+
.addConverterFactory(GsonConverterFactory.create(gson))
56+
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
57+
.client(httpClient)
58+
.build()
59+
}
60+
61+
@Provides
62+
@Singleton
63+
fun provideRestApi(retrofit: Retrofit): RestApi = retrofit.create(RestApi::class.java)
64+
65+
@Provides
66+
@Singleton
67+
fun provideRestClient(restApi: RestApi): RestClient = RestClient(restApi)
68+
69+
@Provides
70+
@Singleton
71+
fun provideWebService(restClient: RestClient): WebService = restClient
72+
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package io.petros.github.data.network
2+
3+
import io.petros.github.domain.model.search.SearchResults
4+
import io.reactivex.Single
5+
6+
interface WebService {
7+
8+
fun search(searchTerm: String): Single<SearchResults>
9+
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.petros.github.data.network.interceptor
2+
3+
import okhttp3.Interceptor
4+
import okhttp3.Response
5+
import javax.inject.Inject
6+
7+
class HeaderInterceptor @Inject constructor() : Interceptor {
8+
9+
companion object {
10+
11+
private const val HEADER_VERSION = "Accept"
12+
private const val HEADER_VERSION_VALUE = "application/vnd.github.v3+json"
13+
14+
}
15+
16+
override fun intercept(chain: Interceptor.Chain): Response {
17+
val newRequest = chain
18+
.request()
19+
.newBuilder()
20+
.addHeader(HEADER_VERSION, HEADER_VERSION_VALUE)
21+
.build()
22+
return chain.proceed(newRequest)
23+
}
24+
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.petros.github.data.network.rest
2+
3+
import io.petros.github.data.network.rest.response.search.SearchResultsResponse
4+
import io.reactivex.Single
5+
import retrofit2.http.GET
6+
import retrofit2.http.Query
7+
8+
interface RestApi {
9+
10+
@GET("search/repositories")
11+
fun search(
12+
@Query("q") searchTerm: String
13+
): Single<SearchResultsResponse>
14+
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.petros.github.data.network.rest
2+
3+
import io.petros.github.data.network.WebService
4+
import io.petros.github.data.network.rest.mapper.search.SearchMapper
5+
import io.petros.github.domain.model.search.SearchResults
6+
import io.reactivex.Single
7+
import javax.inject.Inject
8+
9+
class RestClient @Inject constructor(
10+
private val restApi: RestApi
11+
) : WebService {
12+
13+
override fun search(searchTerm: String): Single<SearchResults> {
14+
return restApi.search(searchTerm)
15+
.map { SearchMapper.transform(it) }
16+
}
17+
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.petros.github.data.network.rest.mapper.search
2+
3+
import io.petros.github.data.network.rest.response.search.Repo
4+
import io.petros.github.data.network.rest.response.search.SearchResultsResponse
5+
import io.petros.github.domain.model.search.Repository
6+
import io.petros.github.domain.model.search.SearchResults
7+
8+
class SearchMapper { // MIT
9+
10+
companion object {
11+
12+
internal fun transform(searchResultsResponse: SearchResultsResponse): SearchResults {
13+
val repositories = arrayListOf<Repository>()
14+
for (repo in searchResultsResponse.items) {
15+
repositories.add(repo.toRepository())
16+
}
17+
return SearchResults(repositories)
18+
}
19+
20+
}
21+
22+
}
23+
24+
fun Repo.toRepository(): Repository {
25+
return Repository(
26+
ownerAvatar = owner.avatar_url,
27+
name = name,
28+
description = description,
29+
numberOfForks = forks_count
30+
)
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package io.petros.github.data.network.rest.response.search
2+
3+
data class Owner(
4+
val avatar_url: String
5+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.petros.github.data.network.rest.response.search
2+
3+
data class Repo(
4+
val name: String,
5+
val owner: Owner,
6+
val description: String?,
7+
val forks_count: Int
8+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package io.petros.github.data.network.rest.response.search
2+
3+
data class SearchResultsResponse(
4+
val items: List<Repo>
5+
)
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package io.petros.github.data.repository.search
22

3+
import io.petros.github.data.network.WebService
34
import io.petros.github.domain.model.search.SearchResults
45
import io.petros.github.domain.repository.search.SearchRepository
56
import io.reactivex.Single
67
import javax.inject.Inject
78

8-
class SearchRepositoryImpl @Inject constructor() : SearchRepository {
9+
class SearchRepositoryImpl @Inject constructor(
10+
private val webService: WebService
11+
) : SearchRepository {
912

10-
override fun search(): Single<SearchResults> {
11-
return Single.just(SearchResults("GitHub Search Results!"))
13+
override fun search(searchTerm: String): Single<SearchResults> {
14+
return webService.search(searchTerm)
1215
}
1316

1417
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<resources>
4+
5+
<integer name="network_timeout">10000</integer>
6+
7+
<string name="rest_github_url">https://api.github.com/</string>
8+
9+
</resources>

domain/src/main/kotlin/io/petros/github/domain/interactor/search/SearchUseCase.kt

+15-3
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,22 @@ import javax.inject.Inject
1010
class SearchUseCase @Inject constructor(
1111
private val searchRepository: SearchRepository,
1212
rxSchedulers: RxSchedulers
13-
) : UseCaseSingle<SearchResults, Unit>(rxSchedulers) {
13+
) : UseCaseSingle<SearchResults, SearchUseCase.Params>(rxSchedulers) {
14+
15+
override fun buildUseCaseObservable(params: Params): Single<SearchResults> {
16+
return searchRepository.search(params.searchTerm)
17+
}
18+
19+
data class Params constructor(val searchTerm: String) {
20+
21+
companion object {
22+
23+
fun with(searchTerm: String): Params {
24+
return Params(searchTerm)
25+
}
26+
27+
}
1428

15-
override fun buildUseCaseObservable(params: Unit): Single<SearchResults> {
16-
return searchRepository.search()
1729
}
1830

1931
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.petros.github.domain.model.search
2+
3+
data class Repository(
4+
val ownerAvatar: String,
5+
val name: String,
6+
val description: String?,
7+
val numberOfForks: Int
8+
)
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package io.petros.github.domain.model.search
22

3-
class SearchResults(
4-
val text: String
3+
data class SearchResults(
4+
val repositories: List<Repository>
55
)

domain/src/main/kotlin/io/petros/github/domain/repository/search/SearchRepository.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ import io.reactivex.Single
55

66
interface SearchRepository {
77

8-
fun search(): Single<SearchResults>
8+
fun search(searchTerm: String): Single<SearchResults>
99

1010
}

presentation/build.gradle

+3
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ dependencies {
6565
kapt deps.di.daggerAndroidProcessor
6666
implementation deps.rx.rxjava
6767
implementation deps.rx.rxandroid
68+
implementation deps.network.gson
69+
implementation deps.network.okhttpLogging
70+
implementation deps.rest.retrofit
6871
implementation deps.logging.timber
6972

7073
testImplementation deps.test.junit

presentation/src/main/AndroidManifest.xml

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
xmlns:tools="http://schemas.android.com/tools"
55
package="io.petros.github">
66

7+
<uses-permission android:name="android.permission.INTERNET"/>
8+
79
<application
810
android:name=".presentation.App"
911
android:allowBackup="false"

presentation/src/main/kotlin/io/petros/github/presentation/di/dagger/AppModule.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ package io.petros.github.presentation.di.dagger
33
import android.content.Context
44
import dagger.Module
55
import dagger.Provides
6+
import io.petros.github.data.di.dagger.NetworkModule
67
import io.petros.github.data.di.dagger.RepositoriesModule
78
import io.petros.github.data.di.dagger.SchedulersModule
89
import io.petros.github.presentation.App
910

1011
@Module(
1112
includes = [
1213
SchedulersModule::class,
13-
RepositoriesModule::class
14+
RepositoriesModule::class,
15+
NetworkModule::class
1416
]
1517
)
1618
class AppModule {

presentation/src/main/kotlin/io/petros/github/presentation/feature/search/SearchActivity.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,19 @@ class SearchActivity : BaseActivity<SearchActivityViewModel>(), SearchToolbarCal
3333
}
3434

3535
private fun showSearchResults(searchResults: SearchResults) {
36-
tv_search_result.text = searchResults.text
36+
tv_search_result.text = searchResults.repositories.size.toString()
3737
}
3838

3939
/* CALLBACK */
4040

4141
override fun onSearch(searchTerm: String) {
42-
search()
42+
search(searchTerm)
4343
}
4444

4545
/* DATA LOADING */
4646

47-
private fun search() {
48-
viewModel.search()
47+
private fun search(searchTerm: String) {
48+
viewModel.search(searchTerm)
4949
}
5050

5151
/* CONTRACT */

0 commit comments

Comments
 (0)