Skip to content

Commit

Permalink
Updating client and server backend
Browse files Browse the repository at this point in the history
  • Loading branch information
mirzemehdi committed Jul 27, 2023
1 parent c724f96 commit edd565a
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 80 deletions.
1 change: 0 additions & 1 deletion backend-server/src/main/kotlin/com/mmk/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.mmk

import com.mmk.di.appModule
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import com.mmk.plugins.*
import io.ktor.server.resources.*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ fun Route.configureQuotesRoutes(quotesDataSource: QuotesDataSource) {
job = launch {
val quotesFlow = quotesDataSource.listenForFirstPageDataChanges(pageLimit = pageLimit)
quotesFlow.collectLatest {
sendSerialized(it)
send(Frame.Text(Json.encodeToString(it)))
}
}
} catch (e: NumberFormatException) {
Expand Down
6 changes: 2 additions & 4 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,6 @@ object Libs {
//Date
val dateTime by lazy { "org.jetbrains.kotlinx:kotlinx-datetime:${Versions.dateTime}" }

//Firebase
val firebaseFireStoreMultiPlatform by lazy { "dev.gitlive:firebase-firestore:${Versions.firebaseMultiPlatform}" }

//Kotlin serializer
val kotlinSerializer by lazy { "org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinSerializer}" }
//Navigation
Expand All @@ -101,7 +98,8 @@ object Libs {
val ktorSerialization by lazy { "io.ktor:ktor-serialization-kotlinx-json:${Versions.ktor}" }
val ktorContentNegotiation by lazy { "io.ktor:ktor-client-content-negotiation:${Versions.ktor}" }
val ktorResources by lazy { "io.ktor:ktor-client-resources:${Versions.ktor}" }
// val logbackClassic by lazy { "ch.qos.logback:logback-classic:1.4.8" }
val ktorWebSocket by lazy { "io.ktor:ktor-client-websockets:${Versions.ktor}" }
val logbackClassic by lazy { "ch.qos.logback:logback-classic:1.2.11" }


}
Expand Down
10 changes: 2 additions & 8 deletions buildSrc/src/main/kotlin/DependencyHandlerExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,11 @@ fun KotlinDependencyHandler.implementNetworkingLibraries() {
implementation(Libs.ktorLogging)
implementation(Libs.ktorSerialization)
implementation(Libs.ktorContentNegotiation)
implementation(Libs.ktorWebSocket)
implementation(Libs.logbackClassic)

}

fun DependencyHandler.implementFirebase() {
implement(platform(Libs.firebasePlatform))
implement(Libs.firebaseFireStore)
implement(Libs.firebaseFireStoreKtx)
implement(Libs.firebaseCoroutineSupport)
implement(Libs.firebaseAnalytics)
implement(Libs.firebaseCrashlytics)
}

fun DependencyHandler.testImplementJunit5() {
testImplement(TestingLibs.junit5JupiterApi)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ internal class AndroidNetworkHandlerTest {
}

@Test
@Config(sdk = [23])
@Config(sdk = [24])
fun givenNoWifiAndNoCellularConnection_hasNetworkConnection_returnsFalse() {
connectivityManager?.let {
shadowOf(connectivityManager).setNetworkCapabilities(
Expand All @@ -68,7 +68,7 @@ internal class AndroidNetworkHandlerTest {
}

@Test
@Config(sdk = [23])
@Config(sdk = [24])
fun givenHasWifiAndNoCellularConnection_hasNetworkConnection_returnsTrue() {
connectivityManager?.let {
shadowOf(networkCapabilities).addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
Expand All @@ -81,7 +81,7 @@ internal class AndroidNetworkHandlerTest {
}

@Test
@Config(sdk = [23])
@Config(sdk = [24])
fun givenNoWifiAndHasCellularConnection_hasNetworkConnection_returnsTrue() {
connectivityManager?.let {
shadowOf(networkCapabilities).addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
Expand All @@ -94,7 +94,7 @@ internal class AndroidNetworkHandlerTest {
}

@Test
@Config(sdk = [23])
@Config(sdk = [24])
fun givenEthernetConnection_hasNetworkConnection_returnsTrue() {
connectivityManager?.let {
shadowOf(networkCapabilities).addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
Expand All @@ -105,32 +105,4 @@ internal class AndroidNetworkHandlerTest {
Truth.assertThat(networkHandler.hasNetworkConnection()).isTrue()
}
}

@Test
@Config(sdk = [21])
fun verifyNoActiveNetworkSdkVersionBelow23_hasNetworkConnection_returnsFalse() {
connectivityManager?.let {
shadowOf(connectivityManager).setActiveNetworkInfo(null)
Truth.assertThat(networkHandler.hasNetworkConnection()).isFalse()
}
}

@Test
@Config(sdk = [21])
fun verifyGivenConnectedNetworkSdkVersionBelow23_hasNetworkConnection_returnsTrue() {
connectivityManager?.let {
every { networkInfo.isConnected } returns true
shadowOf(connectivityManager).setActiveNetworkInfo(networkInfo)
Truth.assertThat(networkHandler.hasNetworkConnection()).isTrue()
}
}
@Test
@Config(sdk = [21])
fun verifyNoConnectedNetworkSdkVersionBelow23_hasNetworkConnection_returnsFalse() {
connectivityManager?.let {
every { networkInfo.isConnected } returns false
shadowOf(connectivityManager).setActiveNetworkInfo(networkInfo)
Truth.assertThat(networkHandler.hasNetworkConnection()).isFalse()
}
}
}
1 change: 0 additions & 1 deletion shared/features/quotes/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ kotlin {
dependencies {
implementation(project(Modules.core))
implementation(project(Modules.commonUi))
implementation(Libs.firebaseFireStoreMultiPlatform)
implementation(Libs.kotlinSerializer)
implementNetworkingLibraries()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,24 @@ package com.mmk.quotes.data.di
import com.mmk.quotes.data.source.remote.QuotesRemoteDataSource
import com.mmk.quotes.data.source.remote.QuotesRemoteDataSourceImpl
import com.mmk.quotes.data.source.remote.apiservice.Constants.BASE_URL
import com.mmk.quotes.data.source.remote.apiservice.Constants.QUOTES_COLLECTION_NAME
import com.mmk.quotes.data.source.remote.apiservice.QuotesApiService
import com.mmk.quotes.data.source.remote.apiservice.QuotesApiServiceImpl
import dev.gitlive.firebase.Firebase
import dev.gitlive.firebase.firestore.FirebaseFirestore
import dev.gitlive.firebase.firestore.firestore
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.plugins.resources.Resources
import io.ktor.client.plugins.websocket.WebSockets
import io.ktor.serialization.kotlinx.KotlinxWebsocketSerializationConverter
import io.ktor.serialization.kotlinx.json.json
import org.koin.core.qualifier.named
import kotlinx.serialization.json.Json
import org.koin.core.module.dsl.factoryOf
import org.koin.dsl.bind
import org.koin.dsl.module

val remoteSourceModule = module {

single { Firebase.firestore }
single {
HttpClient(CIO) {
install(Resources)
Expand All @@ -33,16 +31,20 @@ val remoteSourceModule = module {
level = LogLevel.ALL
}
install(ContentNegotiation) {
json()
json(
Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
}
)
}
install(WebSockets) {
contentConverter = KotlinxWebsocketSerializationConverter(Json)
}
}
}

fun provideQuotesReference(db: FirebaseFirestore) =
db.collection(QUOTES_COLLECTION_NAME)

single(named(QUOTES_COLLECTION_NAME)) { provideQuotesReference(get()) }

factory<QuotesApiService> { QuotesApiServiceImpl(get(named(QUOTES_COLLECTION_NAME)), get()) }
factory<QuotesRemoteDataSource> { QuotesRemoteDataSourceImpl(get(), get()) }
factoryOf(::QuotesApiServiceImpl) bind QuotesApiService::class
factoryOf(::QuotesRemoteDataSourceImpl) bind QuotesRemoteDataSource::class
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ package com.mmk.quotes.data.source.remote.apiservice
object Constants {
const val QUOTES_COLLECTION_NAME = "Quotes"
const val BASE_URL = "https://quotesapp-backend-server.ew.r.appspot.com"
const val BASE_SOCKET_URL = "quotesapp-backend-server.ew.r.appspot.com"
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
package com.mmk.quotes.data.source.remote.apiservice

import com.mmk.core.util.logger.AppLogger
import com.mmk.quotes.data.source.remote.apiservice.Constants.BASE_SOCKET_URL
import com.mmk.quotes.data.source.remote.model.request.NewQuoteRequest
import com.mmk.quotes.data.source.remote.model.response.QuoteResponse
import dev.gitlive.firebase.firestore.CollectionReference
import dev.gitlive.firebase.firestore.Direction
import dev.gitlive.firebase.firestore.FieldPath
import dev.gitlive.firebase.firestore.Query
import dev.gitlive.firebase.firestore.orderBy
import dev.gitlive.firebase.firestore.startAfter
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.websocket.webSocketSession
import io.ktor.client.request.get
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.contentType
import io.ktor.utils.io.CancellationException
import io.ktor.websocket.DefaultWebSocketSession
import io.ktor.websocket.Frame
import io.ktor.websocket.close
import io.ktor.websocket.readText
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.serialization.json.Json

class QuotesApiServiceImpl(
private val quotesCollection: CollectionReference,
private val httpClient: HttpClient,
) : QuotesApiService {
class QuotesApiServiceImpl(private val httpClient: HttpClient) : QuotesApiService {

private var quotesSocket: DefaultWebSocketSession? = null

override suspend fun getQuotesByPagination(
pageIndex: String?,
Expand All @@ -38,9 +47,31 @@ class QuotesApiServiceImpl(
}.body()
}

@OptIn(ExperimentalCoroutinesApi::class)
override fun observeFirstPageQuotes(pageLimit: Int): Flow<List<QuoteResponse>> {
val snapshots = getQuery(null, pageLimit).snapshots
return snapshots.map { snapshot -> snapshot.documents.map { it.data() } }
return flowOf(Unit)
.onStart {
initSocketConnection()
quotesSocket?.send(Frame.Text("$pageLimit"))
}
.flatMapLatest {
quotesSocket?.incoming?.receiveAsFlow() ?: flowOf(Frame.Text(""))
}
.filter {
it is Frame.Text
}
.map {
val json = (it as Frame.Text).readText()
Json.decodeFromString<List<QuoteResponse>>(json)
}
.catch {
emit(getQuotesByPagination(null, pageLimit))
}
.onCompletion {
if (it is CancellationException) {
closeSocketConnection()
}
}
}

override suspend fun addNewQuote(quote: NewQuoteRequest) = networkApiCall {
Expand All @@ -51,12 +82,20 @@ class QuotesApiServiceImpl(
Unit
}

private fun getQuery(pageIndex: String?, pageLimit: Int): Query {
val orderedCollection =
quotesCollection.orderBy(FieldPath("timeStamp"), Direction.DESCENDING)
val query =
pageIndex?.let { orderedCollection.startAfter(it.toLong()) } ?: orderedCollection
return query.limit(pageLimit.toLong())
private suspend fun initSocketConnection() {
try {
if (quotesSocket == null)
quotesSocket =
httpClient
.webSocketSession(host = BASE_SOCKET_URL, path = "/quotes-socket")
} catch (e: Exception) {
e.printStackTrace()
}
}

private suspend fun closeSocketConnection() {
quotesSocket?.close()
quotesSocket = null
}

private suspend fun <T> networkApiCall(block: suspend () -> T): T {
Expand Down

0 comments on commit edd565a

Please sign in to comment.