Skip to content
This repository has been archived by the owner on Jun 14, 2023. It is now read-only.

Display Feeds from API #168

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
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 @@ -19,8 +19,10 @@ import com.android254.data.repos.AuthManager
import com.android254.data.repos.SessionsManager
import com.android254.domain.repos.AuthRepo
import com.android254.domain.repos.SessionsRepo
import com.android254.data.repos.FeedManager
import com.android254.data.repos.HomeRepoImpl
import com.android254.data.repos.SpeakersManager
import com.android254.domain.repos.FeedRepo
import com.android254.domain.repos.HomeRepo
import com.android254.domain.repos.SpeakersRepo
import dagger.Binds
Expand Down Expand Up @@ -48,4 +50,8 @@ abstract class RepoModule {
@Binds
@Singleton
abstract fun provideSpeakersRepo(manager: SpeakersManager): SpeakersRepo

@Binds
@Singleton
abstract fun provideFeedRepo(manager: FeedManager): FeedRepo
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
package com.android254.data.network.apis

import com.android254.data.network.Constants
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 io.ktor.client.*
Expand All @@ -27,8 +27,8 @@ 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("${Constants.EVENT_BASE_URL}/feeds") {
val response: PaginatedResponse<List<FeedDTO>> =
client.get("${Constants.LIVE_BASE_URL}/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
55 changes: 55 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,55 @@
/*
* Copyright 2022 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 api: FeedApi
) : FeedRepo {
override suspend fun fetchFeed(): ResourceResult<List<Feed>> {
return when (val result = api.fetchFeed(1, 100)) {
is DataResult.Empty -> {
ResourceResult.Success(emptyList())
}
is DataResult.Error -> {
ResourceResult.Error(
result.message,
networkError = result.message.contains("network", ignoreCase = true)
)
}
is DataResult.Success -> {
val data = result.data
if (data.isNotEmpty()) {
ResourceResult.Empty()
}
ResourceResult.Success(
data.map { it.toDomain() }
)
}

else -> {
ResourceResult.Success(emptyList())
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2022 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()
)
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
*/
package com.android254.data.network

import com.android254.data.network.models.responses.Feed
import com.android254.data.network.apis.FeedApi
import com.android254.data.network.models.responses.FeedDTO
import com.android254.data.network.util.HttpClientFactory
import com.android254.domain.models.DataResult
import io.ktor.client.engine.mock.*
import io.ktor.http.*
import kotlinx.coroutines.test.runTest
import org.hamcrest.CoreMatchers.*
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test
import java.time.LocalDate
Expand Down Expand Up @@ -94,7 +94,7 @@ class FeedApiTest {
`is`(
DataResult.Success(
listOf(
Feed(
FeedDTO(
title = "Test",
body = "Good one",
topic = "droidconweb",
Expand All @@ -105,7 +105,7 @@ class FeedApiTest {
LocalTime.parse("18:45:49")
)
),
Feed(
FeedDTO(
title = "niko fine",
body = "this is a test",
topic = "droidconweb",
Expand Down
45 changes: 45 additions & 0 deletions data/src/test/java/com/android254/data/repos/FeedManagerTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2022 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.domain.models.DataResult
import com.android254.domain.models.ResourceResult
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
import org.hamcrest.CoreMatchers
import org.hamcrest.MatcherAssert
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class FeedManagerTest {
private val mockApi = mockk<FeedApi>()

@Test
fun `test feeds are fetched successfully`() {
runBlocking {
val repo = FeedManager(mockApi)
coEvery { mockApi.fetchFeed(1, 100) } returns DataResult.Success(feed)
val result = repo.fetchFeed()
coVerify { mockApi.fetchFeed(1, 100) }
MatcherAssert.assertThat(result, CoreMatchers.instanceOf(ResourceResult.Success::class.java))
}
}
}
17 changes: 17 additions & 0 deletions data/src/test/java/com/android254/data/repos/SampleData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
package com.android254.data.repos

import com.android254.data.network.models.responses.*
import com.android254.data.network.models.responses.FeedDTO
import com.android254.data.network.models.responses.PaginatedResponse
import com.android254.data.network.models.responses.PaginationMetaData
import com.android254.data.network.models.responses.ResponseMetaData
import com.android254.data.network.models.responses.SessionDTO
import java.time.LocalDateTime

val results = EventScheduleResponse(
data = mapOf(
Expand All @@ -42,4 +48,15 @@ val results = EventScheduleResponse(
)
)
)
)

val feed = listOf(
FeedDTO(
"Feed",
"Feed feed feed",
"feed",
"",
"",
LocalDateTime.now()
)
)
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 2022 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

data 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 2022 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
Expand Up @@ -15,10 +15,16 @@
*/
package com.android254.presentation.common.bottomnav

import androidx.compose.material3.*
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
Expand All @@ -28,8 +34,6 @@ import com.android254.presentation.common.theme.DroidconKE2022Theme
import com.android254.presentation.common.theme.bottomBlack
import com.android254.presentation.common.theme.bottomOrange
import com.android254.presentation.common.theme.bottomPurple
import androidx.compose.runtime.getValue
import androidx.navigation.NavDestination.Companion.hierarchy

@Composable
fun BottomNavigationBar(navController: NavHostController) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2022 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.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.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class FeedViewModel @Inject constructor(
private val feedRepo: FeedRepo
) : ViewModel() {
private val _feedsState = MutableStateFlow<FeedUIState>(FeedUIState.Loading)
val feedsState: StateFlow<FeedUIState> get() = _feedsState

fun fetchFeed() {
viewModelScope.launch {
when (val value = feedRepo.fetchFeed()) {
is ResourceResult.Error -> _feedsState.value = FeedUIState.Error(value.message)
is ResourceResult.Loading -> _feedsState.value = FeedUIState.Loading
is ResourceResult.Success -> {
value.data?.let {
_feedsState.value = FeedUIState.Success(
it.map { feed -> feed.toPresentation() }
)
}
}
else -> _feedsState.value = FeedUIState.Error("Unknown")
}
}
}
}
Loading