Skip to content

Commit

Permalink
Update toolbar, move coroutines out from usecase, add kotlin flow in …
Browse files Browse the repository at this point in the history
…usecase, implement flow & modify UI, Update gradle & libraries.
  • Loading branch information
ahmedeltaher committed Jun 21, 2020
1 parent b7acec9 commit 28e35bc
Show file tree
Hide file tree
Showing 58 changed files with 467 additions and 716 deletions.
2 changes: 1 addition & 1 deletion CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ include:

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
* The use of serialized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
Expand Down
45 changes: 23 additions & 22 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ android {
buildToolsVersion '29.0.3'
defaultConfig {
applicationId 'com.eltaher.task'
minSdkVersion 16
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
multiDexEnabled true
testInstrumentationRunner "com.task.CustomTestRunner"
}
viewBinding {
enabled = true
buildFeatures{
viewBinding = true
}
buildTypes {
debug {
Expand Down Expand Up @@ -92,7 +92,7 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2'
testImplementation 'org.junit.platform:junit-platform-runner:1.6.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'org.assertj:assertj-core:3.15.0'
androidTestImplementation 'org.assertj:assertj-core:3.16.1'
androidTestImplementation "androidx.arch.core:core-testing:2.1.0"
androidTestImplementation ('androidx.test.espresso:espresso-core:3.2.0',{
exclude group: 'com.android.support', module: 'support-annotations'
Expand All @@ -109,9 +109,11 @@ dependencies {
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.2.0-alpha06'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'com.google.android.material:material:1.3.0-alpha01'
implementation 'androidx.core:core-ktx:1.3.0'
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.cardview:cardview:1.0.0'

//Dagger
implementation 'com.google.dagger:dagger:2.27'
Expand All @@ -122,14 +124,14 @@ dependencies {
kaptAndroidTest 'com.google.dagger:dagger-compiler:2.27'

//Logging
implementation 'com.squareup.okhttp3:logging-interceptor:4.5.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.7.2'

// retrofit
implementation 'com.squareup.retrofit2:retrofit:2.8.1'
implementation 'com.squareup.retrofit2:converter-moshi:2.8.1'
implementation 'com.squareup.moshi:moshi:1.9.2'
kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.9.2'
implementation 'com.squareup.okhttp3:okhttp:4.5.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
implementation 'com.squareup.moshi:moshi:1.9.3'
kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.9.3'
implementation 'com.squareup.okhttp3:okhttp:4.7.2'

//picasso
implementation 'com.squareup.picasso:picasso:2.71828'
Expand All @@ -138,19 +140,18 @@ dependencies {
implementation 'androidx.multidex:multidex:2.0.1'

//Mockk
implementation 'io.mockk:mockk:1.9.3'
implementation 'io.mockk:mockk:1.10.0'

//coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.7'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0-alpha01'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0-alpha01'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0-alpha04'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0-alpha04'

//navigation component
implementation 'androidx.navigation:navigation-fragment:2.2.1'
implementation 'androidx.navigation:navigation-ui:2.2.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.1'
implementation 'androidx.navigation:navigation-fragment:2.2.2'
implementation 'androidx.navigation:navigation-ui:2.2.2'
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
}
2 changes: 1 addition & 1 deletion app/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.task.TestDataReprository.Instance.initData
import com.task.data.DataSource
import com.task.TestDataRepository.Instance.initData
import com.task.data.DataRepositorySource
import com.task.data.Resource
import com.task.data.remote.dto.NewsModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import java.io.InputStream
import javax.inject.Inject

Expand All @@ -16,10 +18,10 @@ import javax.inject.Inject
* Created by AhmedEltaher
*/

class TestDataReprository @Inject constructor() : DataSource {
class TestDataRepository @Inject constructor() : DataRepositorySource {

override suspend fun requestNews(): Resource<NewsModel> {
return Resource.Success(initData())
override suspend fun requestNews(): Flow<Resource<NewsModel>> {
return flow { emit(Resource.Success(initData())) }
}

object Instance {
Expand Down
6 changes: 3 additions & 3 deletions app/src/androidTest/java/com/task/di/TestDataModule.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.task.di

import com.task.TestDataReprository
import com.task.data.DataSource
import com.task.TestDataRepository
import com.task.data.DataRepositorySource
import dagger.Binds
import dagger.Module
import javax.inject.Singleton
Expand All @@ -10,5 +10,5 @@ import javax.inject.Singleton
abstract class TestDataModule {
@Binds
@Singleton
abstract fun provideDataRepository(dataRepository: TestDataReprository): DataSource
abstract fun provideDataRepository(dataRepository: TestDataRepository): DataRepositorySource
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
package com.task.ui.component.news

import android.view.KeyEvent
import android.widget.AutoCompleteTextView
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.IdlingResource
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
import com.task.App
import com.task.R
import org.hamcrest.Matchers
import com.task.ui.component.details.DetailsActivity
import com.task.utils.EspressoIdlingResource
import org.hamcrest.Matchers.not
import org.junit.After
import org.junit.Before
import org.junit.Rule
Expand All @@ -24,59 +27,54 @@ import org.junit.runner.RunWith
@LargeTest
class NewsListActivityTest {
private val testSearchString = "President"

@get:Rule
var mActivityTestRule = ActivityTestRule(NewsListActivity::class.java)
@get:Rule
var detailsActivityTestRule = ActivityTestRule(DetailsActivity::class.java)
private var mIdlingResource: IdlingResource? = null

@Before
fun setup() {
mIdlingResource = mActivityTestRule.activity.countingIdlingResource
IdlingRegistry.getInstance().register(mIdlingResource)
IdlingRegistry.getInstance().register(EspressoIdlingResource.countingIdlingResource)
}

//TODO need to be checked and to be fixed
@Test
fun testSearch() {
onView(withId(R.id.action_search)).perform(click())
onView(isAssignableFrom(AutoCompleteTextView::class.java))
.perform(typeText(testSearchString))
.perform(pressKey(KeyEvent.KEYCODE_ENTER))
onView(withId(R.id.tv_title)).check(matches(isDisplayed()))
onView(withId(R.id.tv_description)).perform(scrollTo())
onView(withId(R.id.tv_description)).check(matches(isDisplayed()))
}
//TODO need to be checked and to be fixed
// @Test
// fun testSearch() {
// val searchEditText = Espresso.onView(ViewMatchers.withId(R.id.et_search))
// searchEditText.perform(ViewActions.click())
// searchEditText.perform(ViewActions.typeText(testSearchString), ViewActions.pressImeActionButton())
// Espresso.onView(ViewMatchers.withId(R.id.btn_search)).perform(ViewActions.click())
// Espresso.onView(ViewMatchers.withId(R.id.tv_title)).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
// Espresso.onView(ViewMatchers.withId(R.id.tv_description)).perform(ViewActions.scrollTo())
// Espresso.onView(ViewMatchers.withId(R.id.tv_description)).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
// Espresso.pressBack()
// searchEditText.check(ViewAssertions.matches(ViewMatchers.withText(testSearchString)))
// Espresso.onView(ViewMatchers.withId(R.id.rv_news_list)).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
// }

@Test
fun testScroll() {
Espresso.onView(ViewMatchers.withId(R.id.rv_news_list))
.perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(0, ViewActions.click()))
Espresso.onView(ViewMatchers.withId(R.id.tv_title)).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
Espresso.onView(ViewMatchers.withId(R.id.tv_description)).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
onView(withId(R.id.rv_news_list))
.perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(0, click()))
onView(withId(R.id.tv_title)).check(matches(isDisplayed()))
onView(withId(R.id.tv_description)).check(matches(isDisplayed()))
}

@Test
fun testRefresh() { //Before refresh there is a list .
Espresso.onView(ViewMatchers.withId(R.id.rv_news_list)).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
Espresso.onView(ViewMatchers.withId(R.id.pb_loading)).check(ViewAssertions.matches(Matchers.not(ViewMatchers.isDisplayed())))
fun testRefresh() {
//Before refresh there is a list .
onView(withId(R.id.rv_news_list)).check(matches(isDisplayed()))
onView(withId(R.id.pb_loading)).check(matches(not(isDisplayed())))
// do refresh .
Espresso.onView(ViewMatchers.withId(R.id.ic_toolbar_refresh)).perform(ViewActions.click())
onView(withId(R.id.action_favorite)).perform(click())
//after refresh there is a list.
Espresso.onView(ViewMatchers.withId(R.id.rv_news_list)).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
Espresso.onView(ViewMatchers.withId(R.id.pb_loading)).check(ViewAssertions.matches(Matchers.not(ViewMatchers.isDisplayed())))
onView(withId(R.id.rv_news_list)).check(matches(isDisplayed()))
onView(withId(R.id.pb_loading)).check(matches(not(isDisplayed())))
}

@Test
fun displayNewsData() {
Espresso.onView(ViewMatchers.withId(R.id.rv_news_list)).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
Espresso.onView(ViewMatchers.withId(R.id.pb_loading)).check(ViewAssertions.matches(Matchers.not(ViewMatchers.isDisplayed())))
}

@Test
fun searchIsActive() {
Espresso.onView(ViewMatchers.withId(R.id.rl_search)).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
Espresso.onView(ViewMatchers.withId(R.id.et_search)).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
Espresso.onView(ViewMatchers.withId(R.id.btn_search)).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
onView(withId(R.id.rv_news_list)).check(matches(isDisplayed()))
onView(withId(R.id.pb_loading)).check(matches(not(isDisplayed())))
}

@After
Expand All @@ -85,13 +83,4 @@ class NewsListActivityTest {
IdlingRegistry.getInstance().unregister()
}
}

fun testEmptySearch() {
val testSearchString = ""
val searchEditText = Espresso.onView(ViewMatchers.withId(R.id.et_search))
searchEditText.perform(ViewActions.click())
searchEditText.perform(ViewActions.typeText(testSearchString), ViewActions.pressImeActionButton())
Espresso.onView(ViewMatchers.withId(R.id.btn_search)).perform(ViewActions.click())
Espresso.onView(Matchers.allOf(ViewMatchers.withId(R.id.snackbar_text), ViewMatchers.withText(App.context.getString(R.string.search_error)))).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
}
}
16 changes: 12 additions & 4 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,24 @@
android:icon="@drawable/news"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
<activity android:name=".ui.component.splash.SplashActivity">
android:theme="@style/ActionBarAppTheme">
<activity android:name=".ui.component.splash.SplashActivity"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".ui.component.news.NewsListActivity"/>
<activity android:name=".ui.component.details.DetailsActivity"/>
<activity android:name=".ui.component.news.NewsListActivity">
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<activity android:name=".ui.component.details.DetailsActivity" />

<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
</application>

</manifest>
1 change: 0 additions & 1 deletion app/src/main/java/com/task/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,5 @@ open class App : MultiDexApplication(), HasAndroidInjector {

companion object {
lateinit var context: Context

}
}
18 changes: 13 additions & 5 deletions app/src/main/java/com/task/data/DataRepository.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.task.data

import com.task.data.local.LocalRepository
import com.task.data.remote.RemoteRepository
import com.task.data.local.LocalData
import com.task.data.remote.RemoteData
import com.task.data.remote.dto.NewsModel
import com.task.utils.wrapEspressoIdlingResource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import javax.inject.Inject


Expand All @@ -11,9 +16,12 @@ import javax.inject.Inject
*/

class DataRepository @Inject
constructor(private val remoteRepository: RemoteRepository, private val localRepository: LocalRepository) : DataSource {
constructor(private val remoteRepository: RemoteData, private val localRepository: LocalData) : DataRepositorySource {

override suspend fun requestNews(): Resource<NewsModel> {
return remoteRepository.requestNews()
override suspend fun requestNews(): Flow<Resource<NewsModel>> {
return flow {
// emit(Resource.Loading())
emit(remoteRepository.requestNews())
}.flowOn(Dispatchers.IO)
}
}
12 changes: 12 additions & 0 deletions app/src/main/java/com/task/data/DataRepositorySource.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.task.data

import com.task.data.remote.dto.NewsModel
import kotlinx.coroutines.flow.Flow

/**
* Created by AhmedEltaher
*/

interface DataRepositorySource {
suspend fun requestNews(): Flow<Resource<NewsModel>>
}
11 changes: 0 additions & 11 deletions app/src/main/java/com/task/data/DataSource.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import javax.inject.Inject
* Created by AhmedEltaher
*/

class LocalRepository @Inject
class LocalData @Inject
constructor()
Loading

0 comments on commit 28e35bc

Please sign in to comment.