Skip to content

Mani: QuotifyApp - Remote call #38

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
74 changes: 74 additions & 0 deletions Mani-Quotify/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import java.util.Locale

plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsKotlinAndroid)
alias(libs.plugins.ksp)
alias(libs.plugins.kotlinSerialization)
jacoco
}

android {
Expand All @@ -22,6 +26,10 @@ android {
}

buildTypes {
debug {
enableAndroidTestCoverage = true
enableUnitTestCoverage = true
}
release {
isMinifyEnabled = false
proguardFiles(
Expand Down Expand Up @@ -63,6 +71,11 @@ dependencies {
implementation(libs.androidx.ui.navigation)
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
implementation(libs.retrofit)
implementation(libs.ktx.sxn.json)
implementation(libs.ktx.sxn.converter)
implementation(libs.okhttp)
implementation(libs.okhttp.logging)
ksp(libs.androidx.room.compiler)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
Expand All @@ -71,4 +84,65 @@ dependencies {
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.mockk)
testImplementation(libs.kotlin.test.junit)
}

val exclusions = listOf(
"**/R.class",
"**/R\$*.class",
"**/BuildConfig.*",
"**/Manifest*.*",
"**/*Test*.*",
"**/com/mani/quotify007/data/*",
"**/com/mani/quotify007/domain/*",
"**/com/mani/quotify007/ui/screens/*"
)

tasks.withType(Test::class) {
configure<JacocoTaskExtension> {
isIncludeNoLocationClasses = true
excludes = listOf("jdk.internal.*", "sun/util/resources/cldr/provider/CLDRLocaleDataMetaInfo")
}
}

android {
applicationVariants.all {
val variant = name.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(
Locale.getDefault()
) else it.toString()
}

val unitTests = "test${variant}UnitTest"
val androidTests = "connected${variant}AndroidTest"

tasks.register<JacocoReport>("Jacoco${variant}CodeCoverage") {
dependsOn(listOf(unitTests, androidTests))
group = "Reporting"
description = "Execute ui and unit tests, generate and combine Jacoco coverage report"
reports {
xml.required.set(true)
html.required.set(true)
}
sourceDirectories.setFrom(
files(
"src/main/java",
"src/$variant/java"
)
)
classDirectories.setFrom(files(
fileTree(layout.buildDirectory.dir("intermediates/javac/")) {
exclude(exclusions)
},
fileTree(layout.buildDirectory.dir("tmp/kotlin-classes/")) {
exclude(exclusions)
}
))
executionData.setFrom(files(
fileTree(layout.buildDirectory) { include(listOf("**/*.exec", "**/*.ec")) }
))
}
}
}
1 change: 1 addition & 0 deletions Mani-Quotify/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".QuotifyApp"
android:allowBackup="true"
Expand Down
20 changes: 6 additions & 14 deletions Mani-Quotify/app/src/main/java/com/mani/quotify007/QuotifyApp.kt
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
package com.mani.quotify007

import android.app.Application
import androidx.room.Room
import com.mani.quotify007.data.local.QuotifyDatabase
import com.mani.quotify007.data.repository.QuoteRepositoryImpl
import com.mani.quotify007.domain.usecase.GetQuoteUseCase
import com.mani.quotify007.ui.screens.utils.QUOTIFY_DB_NAME

class QuotifyApp: Application() {
lateinit var quoteDb: QuotifyDatabase
lateinit var getQuoteUseCase: GetQuoteUseCase
companion object {
/* Hold a static reference to the AppModule instance, which can be accessed from anywhere
* in the application without needing an instance of the Application class */
lateinit var instance: QuotiyAppModule
}
override fun onCreate() {
super.onCreate()
quoteDb = Room.databaseBuilder(
applicationContext,
QuotifyDatabase::class.java,
QUOTIFY_DB_NAME
).build()
val quoteRepository = QuoteRepositoryImpl(quoteDb.favoriteQuoteDao())
getQuoteUseCase = GetQuoteUseCase(repository = quoteRepository)
instance = QuotiyAppModuleImpl(this)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.mani.quotify007

import androidx.room.Room
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.mani.quotify007.data.local.FavoriteQuoteDao
import com.mani.quotify007.data.local.QuotifyDatabase
import com.mani.quotify007.data.remote.QuoteApiService
import com.mani.quotify007.data.remote.getSafeOkHttpClient
import com.mani.quotify007.data.repository.QuoteRepositoryImpl
import com.mani.quotify007.domain.repository.QuoteRepository
import com.mani.quotify007.domain.usecase.GetQuoteUseCase
import com.mani.quotify007.ui.screens.utils.BASE_URL
import com.mani.quotify007.ui.screens.utils.JSON_TYPE
import com.mani.quotify007.ui.screens.utils.QUOTIFY_DB_NAME
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit

interface QuotiyAppModule {
val retrofit: Retrofit
val quoteDb: QuotifyDatabase
val quoteApiService: QuoteApiService
val quoteRepository: QuoteRepository
val quoteUseCase: GetQuoteUseCase
val quoteFavoriteQuoteDao: FavoriteQuoteDao
}

class QuotiyAppModuleImpl(context: QuotifyApp) : QuotiyAppModule {
override val retrofit: Retrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(getSafeOkHttpClient(context).newBuilder().addInterceptor(logging).build())
.addConverterFactory(Json.asConverterFactory(JSON_TYPE.toMediaType()))
.build()
}
override val quoteDb: QuotifyDatabase by lazy {
Room.databaseBuilder(
context,
QuotifyDatabase::class.java,
QUOTIFY_DB_NAME
).build()
}

override val quoteApiService: QuoteApiService by lazy {
retrofit.create(QuoteApiService::class.java)
}
override val quoteRepository: QuoteRepository by lazy {
QuoteRepositoryImpl(quoteApiService, quoteDb)
}
override val quoteUseCase: GetQuoteUseCase by lazy {
GetQuoteUseCase(quoteRepository)
}
override val quoteFavoriteQuoteDao: FavoriteQuoteDao by lazy {
quoteDb.favoriteQuoteDao()
}
}

val logging = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.mani.quotify007.ui.screens.utils.QUOTE_TABLE_NAME

@Entity(tableName = QUOTE_TABLE_NAME)
data class FavoriteQuoteEntity(
@PrimaryKey val id: Int = 0,
val text: String,
@PrimaryKey val id: String,
val content: String,
val author: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.mani.quotify007.data.remote

import com.mani.quotify007.data.remote.model.QuoteNetworkModel
import retrofit2.http.GET

interface QuoteApiService {
@GET("quotes")
suspend fun getQuoteResult(): QuoteNetworkModel
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.mani.quotify007.data.remote

import com.mani.quotify007.QuotifyApp
import com.mani.quotify007.R
import okhttp3.OkHttpClient
import java.security.KeyStore
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager

fun getSafeOkHttpClient(quotifyApp: QuotifyApp): OkHttpClient {
return try {
// Load the trusted certificate
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
load(null, null)
// quotable io trusted certificate
quotifyApp.resources.openRawResource(R.raw.api_quotable_io).use { certInputStream ->
val certificateFactory = java.security.cert.CertificateFactory.getInstance("X.509")
val certificate = certificateFactory.generateCertificate(certInputStream)
setCertificateEntry("api_quotable_io", certificate)
}
}

// Create a TrustManager that trusts the CAs in our KeyStore
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply {
init(keyStore)
}
val trustManagers = trustManagerFactory.trustManagers
val sslContext = SSLContext.getInstance("TLS").apply {
init(null, trustManagers, java.security.SecureRandom())
}
val sslSocketFactory = sslContext.socketFactory

OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustManagers[0] as X509TrustManager)
.build()
} catch (e: Exception) {
throw RuntimeException(e)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.mani.quotify007.data.remote.mapper

import com.mani.quotify007.data.remote.model.QuoteNetworkModel
import com.mani.quotify007.domain.model.Quote
import com.mani.quotify007.domain.model.QuoteResult

fun QuoteNetworkModel.toResults() = QuoteResult(
count = count,
lastItemIndex = lastItemIndex,
page = page,
results = results.map {
Quote(
id = it.id,
author = it.author,
content = it.content,
isFavorite = false
)
},
totalCount = totalCount,
totalPages = totalPages
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.mani.quotify007.data.remote.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class QuoteNetworkModel (
val count: Long,
val totalCount: Long,
val page: Long,
val totalPages: Long,
val lastItemIndex: Long,
val results: List<QuoteItemNetworkModel>
)

@Serializable
data class QuoteItemNetworkModel (
@SerialName("_id")
val id: String,
val author: String,
val content: String,
val tags: List<String>,
val authorSlug: String,
val length: Long,
val dateAdded: String,
val dateModified: String
)
Loading