Skip to content

Commit

Permalink
Feed api implementation (droidconKE#125)
Browse files Browse the repository at this point in the history
* Adding Feed.kt file

* Adding FeedRepo.kt Interface file

* Renaming Feed.kt to FeedDTO.kt

* Adding Feed ViewModel

* Adds feed implementation

* Changing API BaseURL Test

* Adding tests in FeedScreenTest.kt file

* Renamed api -> FeedApi

* Changed ResourceResult<List<Feed>> -> List<Feed>

* Referencing String directly from the composable

* Modifying FeedScreenTest.kt file

* Create droidcon_logo_dark.xml

* check mode in code

* Added codeAnalysis.bat for windows uers and ran it

* set it for all AppBars'

* use if with id

* contrib-readme-action has updated readme (droidconKE#127)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update to AS Flamingo (droidconKE#126)

* updates

* Update AS Version

* Dependencies updates

* update readme

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* rename

* update java version on CI

* contrib-readme-action has updated readme (droidconKE#130)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* contrib-readme-action has updated readme (droidconKE#131)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* contrib-readme-action has updated readme (droidconKE#132)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Migrate some State and viewModel calls from their composables to their respective ViewModels

* contrib-readme-action has updated readme

* contrib-readme-action has updated readme (droidconKE#134)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Adds feed implementation

* Handling state in the FeedScreen.kt file

* Refactoring results

* Fixes failing tests

* Fixes String resource merge conflicts

---------

Co-authored-by: brian.orwe <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Harun Wangereka <[email protected]>
Co-authored-by: yveskalume <[email protected]>
  • Loading branch information
5 people authored Jun 29, 2023
1 parent 8e78fa0 commit 949d8a2
Show file tree
Hide file tree
Showing 18 changed files with 442 additions and 77 deletions.
6 changes: 6 additions & 0 deletions data/src/main/java/com/android254/data/di/RepoModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
package com.android254.data.di

import com.android254.data.repos.AuthManager
import com.android254.data.repos.FeedManager
import com.android254.data.repos.HomeRepoImpl
import com.android254.data.repos.OrganizersSource
import com.android254.data.repos.SessionsManager
import com.android254.data.repos.SpeakersManager
import com.android254.domain.repos.AuthRepo
import com.android254.domain.repos.FeedRepo
import com.android254.domain.repos.HomeRepo
import com.android254.domain.repos.OrganizersRepository
import com.android254.domain.repos.SessionsRepo
Expand Down Expand Up @@ -54,4 +56,8 @@ abstract class RepoModule {
@Binds
@Singleton
abstract fun provideOrganizersRepo(source: OrganizersSource): OrganizersRepository

@Binds
@Singleton
abstract fun provideFeedRepo(manager: FeedManager): FeedRepo
}
14 changes: 7 additions & 7 deletions data/src/main/java/com/android254/data/network/apis/FeedApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@
*/
package com.android254.data.network.apis

import com.android254.data.network.models.responses.Feed
import com.android254.data.network.models.responses.FeedDTO
import com.android254.data.network.models.responses.PaginatedResponse
import com.android254.data.network.util.dataResultSafeApiCall
import com.android254.data.network.util.provideEventBaseUrl
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import com.android254.data.network.util.provideBaseUrl
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import javax.inject.Inject

class FeedApi @Inject constructor(private val client: HttpClient) {

suspend fun fetchFeed(page: Int = 1, size: Int = 100) = dataResultSafeApiCall {
val response: PaginatedResponse<List<Feed>> =
client.get("${provideEventBaseUrl()}/feeds") {
val response: PaginatedResponse<List<FeedDTO>> =
client.get("${provideBaseUrl()}/feeds") {
url {
parameters.append("page", page.toString())
parameters.append("per_page", size.toString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

@Serializable
data class Feed(
data class FeedDTO(
val title: String,
val body: String,
val topic: String,
Expand Down
37 changes: 37 additions & 0 deletions data/src/main/java/com/android254/data/repos/FeedManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2023 DroidconKE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android254.data.repos

import com.android254.data.network.apis.FeedApi
import com.android254.data.repos.mappers.toDomain
import com.android254.domain.models.DataResult
import com.android254.domain.models.Feed
import com.android254.domain.models.ResourceResult
import com.android254.domain.repos.FeedRepo
import javax.inject.Inject

class FeedManager @Inject constructor(
private val FeedApi: FeedApi
) : FeedRepo {
override suspend fun fetchFeed(): ResourceResult<List<Feed>> {
return when (val result = FeedApi.fetchFeed(1, 100)) {
DataResult.Empty -> ResourceResult.Empty("Empty list ")
is DataResult.Error -> ResourceResult.Error(result.message)
is DataResult.Loading -> ResourceResult.Loading(true)
is DataResult.Success -> ResourceResult.Success(result.data.map { it.toDomain() })
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2023 DroidconKE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android254.data.repos.mappers

import com.android254.data.network.models.responses.FeedDTO
import com.android254.domain.models.Feed

fun FeedDTO.toDomain() = Feed(
title = title,
body = body,
topic = topic,
url = url,
image = image,
createdAt = createdAt.toString()
)
18 changes: 11 additions & 7 deletions data/src/test/java/com/android254/data/network/apis/FeedApiTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@
*/
package com.android254.data.network.apis

import com.android254.data.network.models.responses.Feed
import com.android254.data.network.models.responses.FeedDTO
import com.android254.data.network.util.HttpClientFactory
import com.android254.data.network.util.MockTokenProvider
import com.android254.data.network.util.RemoteFeatureToggle
import com.android254.data.network.util.provideEventBaseUrl
import com.android254.data.network.util.provideBaseUrl
import com.android254.domain.models.DataResult
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import io.ktor.client.engine.mock.*
import io.ktor.http.*
import io.ktor.client.engine.mock.MockEngine
import io.ktor.client.engine.mock.respond
import io.ktor.client.engine.mock.respondOk
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.http.headersOf
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.hamcrest.CoreMatchers.`is`
Expand Down Expand Up @@ -53,7 +57,7 @@ class FeedApiTest {

assertThat(mockEngine.requestHistory.size, `is`(1))
mockEngine.requestHistory.first().run {
val expectedUrl = "${provideEventBaseUrl()}/feeds?page=2&per_page=50"
val expectedUrl = "${provideBaseUrl()}/feeds?page=2&per_page=50"
assertThat(url.toString(), `is`(expectedUrl))
assertThat(method, `is`(HttpMethod.Get))
}
Expand Down Expand Up @@ -108,7 +112,7 @@ class FeedApiTest {
`is`(
DataResult.Success(
listOf(
Feed(
FeedDTO(
title = "Test",
body = "Good one",
topic = "droidconweb",
Expand All @@ -119,7 +123,7 @@ class FeedApiTest {
LocalTime.parse("18:45:49")
)
),
Feed(
FeedDTO(
title = "niko fine",
body = "this is a test",
topic = "droidconweb",
Expand Down
25 changes: 25 additions & 0 deletions domain/src/main/java/com/android254/domain/models/Feed.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2023 DroidconKE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android254.domain.models

class Feed(
val title: String,
val body: String,
val topic: String,
val url: String,
val image: String?,
val createdAt: String
)
23 changes: 23 additions & 0 deletions domain/src/main/java/com/android254/domain/repos/FeedRepo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2023 DroidconKE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android254.domain.repos

import com.android254.domain.models.Feed
import com.android254.domain.models.ResourceResult

interface FeedRepo {
suspend fun fetchFeed(): ResourceResult<List<Feed>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2023 DroidconKE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android254.presentation.common.bottomsheet

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.R
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext

@Immutable
@kotlin.jvm.JvmInline
value class Strings private constructor(@Suppress("unused") private val value: Int) {
companion object {
val NavigationMenu = Strings(0)
val CloseDrawer = Strings(1)
val CloseSheet = Strings(2)
val DefaultErrorMessage = Strings(3)
val ExposedDropdownMenu = Strings(4)
val SliderRangeStart = Strings(5)
val SliderRangeEnd = Strings(6)
}
}

@Composable
fun getString(string: Strings): String {
LocalConfiguration.current
val resources = LocalContext.current.resources
return when (string) {
Strings.NavigationMenu -> resources.getString(R.string.navigation_menu)
Strings.CloseDrawer -> resources.getString(R.string.close_drawer)
Strings.CloseSheet -> resources.getString(R.string.close_sheet)
Strings.DefaultErrorMessage -> resources.getString(R.string.default_error_message)
Strings.ExposedDropdownMenu -> resources.getString(R.string.dropdown_menu)
Strings.SliderRangeStart -> resources.getString(R.string.range_start)
Strings.SliderRangeEnd -> resources.getString(R.string.range_end)
else -> ""
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2023 DroidconKE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android254.presentation.feed

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.android254.domain.models.ResourceResult
import com.android254.domain.repos.FeedRepo
import com.android254.presentation.feed.view.FeedUIState
import com.android254.presentation.feed.view.toPresentation
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class FeedViewModel @Inject constructor(
private val feedRepo: FeedRepo
) : ViewModel() {
var viewState: FeedUIState by mutableStateOf(FeedUIState.Loading)
private set

fun fetchFeed() {
viewModelScope.launch {
viewState = when (val value = feedRepo.fetchFeed()) {
is ResourceResult.Empty -> FeedUIState.Empty
is ResourceResult.Error -> FeedUIState.Error(value.message)
is ResourceResult.Loading -> FeedUIState.Loading
is ResourceResult.Success -> FeedUIState.Success(
value.data?.map { it.toPresentation() }
?: emptyList()
)
}
}
}
}
Loading

0 comments on commit 949d8a2

Please sign in to comment.