From f655f41ae476d89543f59644b66a0b13a7db1800 Mon Sep 17 00:00:00 2001 From: sangeetha Date: Fri, 31 Jul 2020 09:12:06 +0530 Subject: [PATCH 01/15] Add Initial Setup --- .idea/codeStyles/Project.xml | 3 - .idea/jarRepositories.xml | 25 +++++ app/build.gradle | 6 ++ .../newsflash/constants/NewsConstants.kt | 2 + .../androiddevs/newsflash/data/NewsError.kt | 14 +-- .../androiddevs/newsflash/data/NewsResult.kt | 41 +++------ .../androiddevs/newsflash/data/NewsSources.kt | 29 ++---- .../newsflash/network/ApiClient.kt | 17 ++++ .../newsflash/network/ApiResponse.kt | 40 ++++++++ .../newsflash/network/LiveDataCallAdapter.kt | 39 ++++++++ .../network/LiveDataCallAdapterFactory.kt | 30 ++++++ .../newsflash/network/NewsApiService.kt | 42 +++++++++ .../newsflash/network/ServiceProvider.kt | 6 ++ .../newsflash/repository/NewsApiService.kt | 92 ------------------- .../newsflash/repository/NewsRepository.kt | 4 + .../newsflash/scope/ActivityScope.kt | 8 ++ .../newsflash/scope/ApplicationScope.kt | 7 ++ .../newsflash/view/MainActivity.kt | 16 +--- .../view/adapter/HomeNewsRecyelerAdapter.kt | 4 +- .../newsflash/view/fragments/HomeFragment.kt | 52 +---------- .../ApplicationViewModelFactory.kt | 25 +++++ .../viewModel/HomeFragmentViewModel.kt | 7 ++ 22 files changed, 293 insertions(+), 216 deletions(-) create mode 100644 .idea/jarRepositories.xml create mode 100644 app/src/main/java/com/androiddevs/newsflash/constants/NewsConstants.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/network/ApiClient.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/network/ApiResponse.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/network/LiveDataCallAdapter.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/network/LiveDataCallAdapterFactory.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/network/NewsApiService.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/network/ServiceProvider.kt delete mode 100644 app/src/main/java/com/androiddevs/newsflash/repository/NewsApiService.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/scope/ActivityScope.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/scope/ApplicationScope.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/viewModeFactory/ApplicationViewModelFactory.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 45b5654..88ea3aa 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,8 +1,5 @@ - - diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..a5f05cd --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index c339f69..9a77f47 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,4 +69,10 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + // DAGGER-ANDROID + implementation 'com.google.dagger:dagger-android:2.23.2' + implementation 'com.google.dagger:dagger-android-support:2.23.2' + kapt 'com.google.dagger:dagger-android-processor:2.23.2' + kapt 'com.google.dagger:dagger-compiler:2.28' } diff --git a/app/src/main/java/com/androiddevs/newsflash/constants/NewsConstants.kt b/app/src/main/java/com/androiddevs/newsflash/constants/NewsConstants.kt new file mode 100644 index 0000000..038d80c --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/constants/NewsConstants.kt @@ -0,0 +1,2 @@ +package com.androiddevs.newsflash.constants + diff --git a/app/src/main/java/com/androiddevs/newsflash/data/NewsError.kt b/app/src/main/java/com/androiddevs/newsflash/data/NewsError.kt index 257a110..60dd15d 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/NewsError.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/NewsError.kt @@ -1,15 +1,9 @@ package com.androiddevs.newsflash.data -import com.google.gson.annotations.SerializedName - -object NewsError -{ +object NewsError { data class Error( - @SerializedName("status") - val status: String, - @SerializedName("code") - val code: String, - @SerializedName("message") - val message: String + val status: String? = null, + val code: String? = null, + val message: String? = null ) } \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/NewsResult.kt b/app/src/main/java/com/androiddevs/newsflash/data/NewsResult.kt index 7418dc8..cf7016e 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/NewsResult.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/NewsResult.kt @@ -1,43 +1,28 @@ package com.androiddevs.newsflash.data -import com.google.gson.annotations.SerializedName - object NewsResult { data class News( - @SerializedName("status") - val status: String, - @SerializedName("totalResults") - val totalResults: Int, - @SerializedName("articles") - val articles: List
+ val status: String? = null, + val totalResults: Int = 0, + val articles: List
= listOf() ) { data class Article( - @SerializedName("source") - val source: Source, - @SerializedName("author") - val author: String, - @SerializedName("title") - val title: String, - @SerializedName("description") - val description: String, - @SerializedName("url") - val url: String, - @SerializedName("urlToImage") - val urlToImage: String, - @SerializedName("publishedAt") - val publishedAt: String, - @SerializedName("content") - val content: String + val source: Source? = null, + val author: String? = null, + val title: String? = null, + val description: String? = null, + val url: String? = null, + val urlToImage: String? = null, + val publishedAt: String? = null, + val content: String? = null ) { data class Source( - @SerializedName("id") - val id: String, - @SerializedName("name") - val name: String + val id: String? = null, + val name: String? = null ) } } diff --git a/app/src/main/java/com/androiddevs/newsflash/data/NewsSources.kt b/app/src/main/java/com/androiddevs/newsflash/data/NewsSources.kt index a3b34b8..23f91c8 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/NewsSources.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/NewsSources.kt @@ -1,32 +1,21 @@ package com.androiddevs.newsflash.data -import com.google.gson.annotations.SerializedName - object NewsSources { data class Sources( - @SerializedName("status") - val status: String, - @SerializedName("sources") - val sources: List + val status: String? = null, + val sources: List = listOf() ) { data class Source( - @SerializedName("id") - val id: String, - @SerializedName("name") - val name: String, - @SerializedName("description") - val description: String, - @SerializedName("url") - val url: String, - @SerializedName("category") - val category: String, - @SerializedName("language") - val language: String, - @SerializedName("country") - val country: String + val id: String? = null, + val name: String? = null, + val description: String? = null, + val url: String? = null, + val category: String? = null, + val language: String? = null, + val country: String? = null ) } } \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/network/ApiClient.kt b/app/src/main/java/com/androiddevs/newsflash/network/ApiClient.kt new file mode 100644 index 0000000..169e61a --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/network/ApiClient.kt @@ -0,0 +1,17 @@ +package com.androiddevs.newsflash.network + +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +object ApiClient { + private val mRetrofitClient: Retrofit by lazy { + Retrofit.Builder() + .addCallAdapterFactory(LiveDataCallAdapterFactory()) + .addConverterFactory(GsonConverterFactory.create()) + .baseUrl("https://newsapi.org/v2/") + .build() + } + + fun getNetworkClient(): Retrofit = mRetrofitClient + +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/network/ApiResponse.kt b/app/src/main/java/com/androiddevs/newsflash/network/ApiResponse.kt new file mode 100644 index 0000000..3a80276 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/network/ApiResponse.kt @@ -0,0 +1,40 @@ +package com.androiddevs.newsflash.network + +import retrofit2.Call +import retrofit2.Response + +class ApiResponse { + + var code: Int = 0 + var body: T? + var errorMessage: String? = null + var shouldShowRetryButton: Boolean = false + private var retrofitCall: Call + + constructor(error: Throwable, call: Call) { + code = 500 + body = null + errorMessage = error.message + shouldShowRetryButton = true + this.retrofitCall = call + } + + constructor(response: Response, call: Call) { + this.retrofitCall = call + code = response.code() + if (response.isSuccessful) { + body = response.body() + } else { + shouldShowRetryButton = true + var message: String? = null + response.errorBody()?.let { + message = it.string() + } + if (message.isNullOrEmpty()) { + message = response.message() + } + errorMessage = message + body = null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/network/LiveDataCallAdapter.kt b/app/src/main/java/com/androiddevs/newsflash/network/LiveDataCallAdapter.kt new file mode 100644 index 0000000..a6263d8 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/network/LiveDataCallAdapter.kt @@ -0,0 +1,39 @@ +package com.androiddevs.newsflash.network + +import androidx.lifecycle.LiveData +import retrofit2.Call +import retrofit2.CallAdapter +import retrofit2.Callback +import retrofit2.Response +import java.lang.reflect.Type +import java.util.concurrent.atomic.AtomicBoolean + +class LiveDataCallAdapter(private var responseType: Type) : CallAdapter>> { + + override fun responseType(): Type { + return responseType + } + + override fun adapt(call: Call): LiveData> { + return object : LiveData>() { + var started = AtomicBoolean(false) + override fun onActive() { + super.onActive() + if (started.compareAndSet(false, true)) { + call.enqueue(object : Callback { + override fun onResponse(call: Call, + response: Response + ) { + postValue(ApiResponse(response, call)) + } + + override fun onFailure(call: Call, throwable: Throwable) { + postValue(ApiResponse(throwable, call)) + } + }) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/network/LiveDataCallAdapterFactory.kt b/app/src/main/java/com/androiddevs/newsflash/network/LiveDataCallAdapterFactory.kt new file mode 100644 index 0000000..4fdc49e --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/network/LiveDataCallAdapterFactory.kt @@ -0,0 +1,30 @@ +package com.androiddevs.newsflash.network + +import androidx.lifecycle.LiveData +import retrofit2.CallAdapter +import retrofit2.Retrofit +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type + +class LiveDataCallAdapterFactory : CallAdapter.Factory() { + + override fun get(returnType: Type, annotations: Array?, + retrofit: Retrofit?): CallAdapter<*, *>? { + if (CallAdapter.Factory.getRawType(returnType) != LiveData::class.java) { + return null + } + + val observableType = CallAdapter.Factory.getParameterUpperBound(0, returnType as ParameterizedType) + val rawObservableType = CallAdapter.Factory.getRawType(observableType) + + if (rawObservableType != ApiResponse::class.java) { + throw IllegalArgumentException("type must be a resource") + } + if (observableType !is ParameterizedType) { + throw IllegalArgumentException("resource must be parameterized") + } + + val bodyType = CallAdapter.Factory.getParameterUpperBound(0, observableType) + return LiveDataCallAdapter(bodyType) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/network/NewsApiService.kt b/app/src/main/java/com/androiddevs/newsflash/network/NewsApiService.kt new file mode 100644 index 0000000..230e0b8 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/network/NewsApiService.kt @@ -0,0 +1,42 @@ +package com.androiddevs.newsflash.network + + +import androidx.lifecycle.LiveData +import com.androiddevs.newsflash.data.NewsResult +import com.androiddevs.newsflash.data.NewsSources +import retrofit2.http.GET +import retrofit2.http.Query + +interface NewsApiService { + @GET("sources") + fun getNewsSources( + @Query("category") category: String, @Query("language") language: String, + @Query("country") country: String + ): LiveData> + + @GET("top-headlines") + fun getTopHeadlines( + @Query("country") country: String, + @Query("category") category: String, + @Query("sources") sources: String, + @Query("q") keyword: String, + @Query("pageSize") pageSize: Int, + @Query("page") page: Int + ): LiveData> + + @GET("everything") + fun getEverything( + @Query("q") keyword: String, + @Query("qInTitle") keyWordInArtcile: String, + @Query("sources") sources: String, + @Query("domains") domain: String, + @Query("excludeDomains") excludeDomains: String, + @Query("from") from: String, + @Query("to") to: String, + @Query("language") language: String, + @Query("sortBy") sortBy: String, + @Query("pageSize") pageSize: Int, + @Query("page") page: Int + ): LiveData> + +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/network/ServiceProvider.kt b/app/src/main/java/com/androiddevs/newsflash/network/ServiceProvider.kt new file mode 100644 index 0000000..be232a0 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/network/ServiceProvider.kt @@ -0,0 +1,6 @@ +package com.androiddevs.newsflash.network + +class ServiceProvider { + + fun provideQuizService(): NewsApiService = ApiClient.getNetworkClient().create(NewsApiService::class.java) +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/repository/NewsApiService.kt b/app/src/main/java/com/androiddevs/newsflash/repository/NewsApiService.kt deleted file mode 100644 index d7ef33f..0000000 --- a/app/src/main/java/com/androiddevs/newsflash/repository/NewsApiService.kt +++ /dev/null @@ -1,92 +0,0 @@ -package com.androiddevs.newsflash.repository - - -import com.androiddevs.newsflash.BuildConfig -import com.androiddevs.newsflash.data.NewsResult -import com.androiddevs.newsflash.data.NewsSources -import io.reactivex.Observable -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import okhttp3.Response -import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Retrofit -import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory -import retrofit2.converter.gson.GsonConverterFactory -import retrofit2.http.GET -import retrofit2.http.Query -import java.util.concurrent.TimeUnit - -interface NewsApiService { - @GET("sources") - fun getNewsSources( - @Query("category") category: String, - @Query("language") language: String, - @Query("country") country: String - ): Observable - - @GET("top-headlines") - fun getTopHeadlines( - @Query("country") country: String, - @Query("category") category: String, - @Query("sources") sources: String, - @Query("q") keyword: String, - @Query("pageSize") pageSize: Int, - @Query("page") page: Int - ): Observable - - @GET("everything") - fun getEverything( - @Query("q") keyword: String, - @Query("qInTitle") keyWordInArtcile : String, - @Query("sources") sources: String, - @Query("domains") domain: String, - @Query("excludeDomains") excludeDomains: String, - @Query("from") from: String, - @Query("to") to: String, - @Query("language") language:String, - @Query("sortBy") sortBy:String, - @Query("pageSize") pageSize: Int, - @Query("page") page: Int):Observable - - - companion object { - fun create(): NewsApiService { - - val httpLoggingInterceptor = HttpLoggingInterceptor() - httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY - - val okHttpClient = OkHttpClient.Builder() - .addInterceptor(httpLoggingInterceptor) - .addInterceptor(object : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - val original = chain.request() - - val request = original.newBuilder() - .addHeader("X-Api-Key", BuildConfig.API_KEY) - .method(original.method, original.body) - .build() - - return chain.proceed(request) - } - }) - .connectTimeout(30, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .writeTimeout(30, TimeUnit.SECONDS) - .build() - - val retrofit = Retrofit.Builder() - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .addConverterFactory(GsonConverterFactory.create()) - .baseUrl("https://newsapi.org/v2/") - .client(okHttpClient) - .build() - - - return retrofit.create(NewsApiService::class.java) - - } - - } - - -} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt b/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt new file mode 100644 index 0000000..7c043a0 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt @@ -0,0 +1,4 @@ +package com.androiddevs.newsflash.repository + +class NewsRepository { +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/scope/ActivityScope.kt b/app/src/main/java/com/androiddevs/newsflash/scope/ActivityScope.kt new file mode 100644 index 0000000..5b5a0a6 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/scope/ActivityScope.kt @@ -0,0 +1,8 @@ +package com.androiddevs.newsflash.scope + +import javax.inject.Scope + +@MustBeDocumented +@Scope +@Retention(AnnotationRetention.RUNTIME) +annotation class ActivityScope \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/scope/ApplicationScope.kt b/app/src/main/java/com/androiddevs/newsflash/scope/ApplicationScope.kt new file mode 100644 index 0000000..90fb894 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/scope/ApplicationScope.kt @@ -0,0 +1,7 @@ +package com.androiddevs.newsflash.scope + +import javax.inject.Scope + +@Scope +@Retention(AnnotationRetention.RUNTIME) +annotation class ApplicationScope \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/view/MainActivity.kt b/app/src/main/java/com/androiddevs/newsflash/view/MainActivity.kt index 85370ea..6fc3760 100644 --- a/app/src/main/java/com/androiddevs/newsflash/view/MainActivity.kt +++ b/app/src/main/java/com/androiddevs/newsflash/view/MainActivity.kt @@ -2,24 +2,12 @@ package com.androiddevs.newsflash.view import androidx.appcompat.app.AppCompatActivity import android.os.Bundle -import android.util.Log import com.androiddevs.newsflash.R -import com.androiddevs.newsflash.repository.NewsApiService -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers -class MainActivity : AppCompatActivity() -{ +class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) - { + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - - - - - } } diff --git a/app/src/main/java/com/androiddevs/newsflash/view/adapter/HomeNewsRecyelerAdapter.kt b/app/src/main/java/com/androiddevs/newsflash/view/adapter/HomeNewsRecyelerAdapter.kt index 8d473c4..c916382 100644 --- a/app/src/main/java/com/androiddevs/newsflash/view/adapter/HomeNewsRecyelerAdapter.kt +++ b/app/src/main/java/com/androiddevs/newsflash/view/adapter/HomeNewsRecyelerAdapter.kt @@ -10,8 +10,8 @@ import com.androiddevs.newsflash.data.NewsResult import com.androiddevs.newsflash.databinding.HomeNewsRowBinding -class HomeNewsRecyelerAdapter(private var newsList: ArrayList) : - RecyclerView.Adapter() { +class HomeNewsRecyclerAdapter(private var newsList: ArrayList) : + RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val layoutInflater = LayoutInflater.from(parent.context) diff --git a/app/src/main/java/com/androiddevs/newsflash/view/fragments/HomeFragment.kt b/app/src/main/java/com/androiddevs/newsflash/view/fragments/HomeFragment.kt index 90d45f3..a7c1d3b 100644 --- a/app/src/main/java/com/androiddevs/newsflash/view/fragments/HomeFragment.kt +++ b/app/src/main/java/com/androiddevs/newsflash/view/fragments/HomeFragment.kt @@ -2,56 +2,14 @@ package com.androiddevs.newsflash.view.fragments import android.os.Bundle -import android.util.Log -import androidx.fragment.app.Fragment -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import androidx.databinding.DataBindingUtil -import androidx.recyclerview.widget.LinearLayoutManager - +import androidx.fragment.app.Fragment import com.androiddevs.newsflash.R -import com.androiddevs.newsflash.data.NewsResult -import com.androiddevs.newsflash.databinding.FragmentHomeBinding -import com.androiddevs.newsflash.databinding.FragmentHomeBinding.inflate -import com.androiddevs.newsflash.databinding.HomeNewsRowBinding -import com.androiddevs.newsflash.repository.NewsApiService -import com.androiddevs.newsflash.view.adapter.HomeNewsRecyelerAdapter -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers -import kotlinx.android.synthetic.main.fragment_home.* -/** - * A simple [Fragment] subclass. - */ -class HomeFragment : Fragment() { - private val newsApiService by lazy { NewsApiService.create() } - private lateinit var disposable: Disposable - private lateinit var newsList: ArrayList - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val homeBinding = DataBindingUtil.inflate( - inflater, - R.layout.fragment_home, - container, - false - ) - newsList = ArrayList() - disposable = newsApiService.getTopHeadlines("us", "", "", "", 10, 0) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ result -> - for (article in result.articles) { - newsList.add(article) - } - homeRecyclerView.adapter = HomeNewsRecyelerAdapter(newsList) - homeRecyclerView.layoutManager = LinearLayoutManager(context) - }, { error -> Log.v("Error", error.message.toString()) }) - return homeBinding.root - } +class HomeFragment : Fragment(R.layout.fragment_home) { + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + } } diff --git a/app/src/main/java/com/androiddevs/newsflash/viewModeFactory/ApplicationViewModelFactory.kt b/app/src/main/java/com/androiddevs/newsflash/viewModeFactory/ApplicationViewModelFactory.kt new file mode 100644 index 0000000..0694abb --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/viewModeFactory/ApplicationViewModelFactory.kt @@ -0,0 +1,25 @@ +package com.androiddevs.newsflash.viewModeFactory + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import javax.inject.Inject +import javax.inject.Provider + +@Suppress("UNCHECKED_CAST") +class ApplicationViewModelFactory +@Inject constructor( + private val creators: Map, @JvmSuppressWildcards Provider> +) : ViewModelProvider.Factory { + + override fun create(modelClass: Class): T { + val creator = creators[modelClass] ?: creators.asIterable() + .firstOrNull { modelClass.isAssignableFrom(it.key) }?.value + ?: throw IllegalArgumentException("unknown model class $modelClass") + return try { + creator.get() as T + } catch (e: Exception) { + throw RuntimeException(e) + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt b/app/src/main/java/com/androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt new file mode 100644 index 0000000..2fa5197 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt @@ -0,0 +1,7 @@ +package com.androiddevs.newsflash.viewModel + +import androidx.lifecycle.ViewModel + +class HomeFragmentViewModel(): ViewModel() { + +} \ No newline at end of file From 764389a0d2d0079fef1001f5b3adb88b2a349a2c Mon Sep 17 00:00:00 2001 From: sangeetha Date: Fri, 31 Jul 2020 13:16:12 +0530 Subject: [PATCH 02/15] Initial Commit --- app/.gitignore | 1 - .../com/androiddevs/newsflash/repository/NewsRepository.kt | 3 +-- .../com/androiddevs/newsflash/view/fragments/HomeFragment.kt | 3 --- .../androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt | 4 +--- 4 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 app/.gitignore diff --git a/app/.gitignore b/app/.gitignore deleted file mode 100644 index 796b96d..0000000 --- a/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt b/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt index 7c043a0..50da9c9 100644 --- a/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt +++ b/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt @@ -1,4 +1,3 @@ package com.androiddevs.newsflash.repository -class NewsRepository { -} \ No newline at end of file +class NewsRepository \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/view/fragments/HomeFragment.kt b/app/src/main/java/com/androiddevs/newsflash/view/fragments/HomeFragment.kt index a7c1d3b..da2b7f1 100644 --- a/app/src/main/java/com/androiddevs/newsflash/view/fragments/HomeFragment.kt +++ b/app/src/main/java/com/androiddevs/newsflash/view/fragments/HomeFragment.kt @@ -9,7 +9,4 @@ import com.androiddevs.newsflash.R class HomeFragment : Fragment(R.layout.fragment_home) { - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - } } diff --git a/app/src/main/java/com/androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt b/app/src/main/java/com/androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt index 2fa5197..35d3ee7 100644 --- a/app/src/main/java/com/androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt +++ b/app/src/main/java/com/androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt @@ -2,6 +2,4 @@ package com.androiddevs.newsflash.viewModel import androidx.lifecycle.ViewModel -class HomeFragmentViewModel(): ViewModel() { - -} \ No newline at end of file +class HomeFragmentViewModel : ViewModel() \ No newline at end of file From a88cb4c1a6dd61f267dc33b1d1772f0787a2edb8 Mon Sep 17 00:00:00 2001 From: sangeetha Date: Tue, 4 Aug 2020 19:27:08 +0530 Subject: [PATCH 03/15] Initial Setup --- .../newsflash/constants/NewsConstants.kt | 1 + .../newsflash/network/NewsApiService.kt | 4 +++ .../newsflash/network/ServiceProvider.kt | 2 +- .../newsflash/repository/NewsRepository.kt | 15 ++++++++- .../fragments/BusinessHeadlineFragment.kt | 20 ++++++++++++ .../viewModel/BusinessHeadlineViewModel.kt | 16 ++++++++++ .../res/layout/fragment_business_headline.xml | 15 +++++++++ app/src/main/res/layout/fragment_home.xml | 11 ++++--- app/src/main/res/layout/home_news_row.xml | 8 ++--- app/src/main/res/layout/item_headlines.xml | 31 +++++++++++++++++++ 10 files changed, 113 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/com/androiddevs/newsflash/view/fragments/BusinessHeadlineFragment.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/viewModel/BusinessHeadlineViewModel.kt create mode 100644 app/src/main/res/layout/fragment_business_headline.xml create mode 100644 app/src/main/res/layout/item_headlines.xml diff --git a/app/src/main/java/com/androiddevs/newsflash/constants/NewsConstants.kt b/app/src/main/java/com/androiddevs/newsflash/constants/NewsConstants.kt index 038d80c..3c2d7da 100644 --- a/app/src/main/java/com/androiddevs/newsflash/constants/NewsConstants.kt +++ b/app/src/main/java/com/androiddevs/newsflash/constants/NewsConstants.kt @@ -1,2 +1,3 @@ package com.androiddevs.newsflash.constants +const val API_KEY = "83002035a125490c9b1cd28fedbd1945" \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/network/NewsApiService.kt b/app/src/main/java/com/androiddevs/newsflash/network/NewsApiService.kt index 230e0b8..fe7d331 100644 --- a/app/src/main/java/com/androiddevs/newsflash/network/NewsApiService.kt +++ b/app/src/main/java/com/androiddevs/newsflash/network/NewsApiService.kt @@ -39,4 +39,8 @@ interface NewsApiService { @Query("page") page: Int ): LiveData> + @GET("top-headlines") + fun getBusinessNews( + + ) } \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/network/ServiceProvider.kt b/app/src/main/java/com/androiddevs/newsflash/network/ServiceProvider.kt index be232a0..1513aa6 100644 --- a/app/src/main/java/com/androiddevs/newsflash/network/ServiceProvider.kt +++ b/app/src/main/java/com/androiddevs/newsflash/network/ServiceProvider.kt @@ -2,5 +2,5 @@ package com.androiddevs.newsflash.network class ServiceProvider { - fun provideQuizService(): NewsApiService = ApiClient.getNetworkClient().create(NewsApiService::class.java) + fun provideNewsService(): NewsApiService = ApiClient.getNetworkClient().create(NewsApiService::class.java) } \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt b/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt index 50da9c9..5c5038e 100644 --- a/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt +++ b/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt @@ -1,3 +1,16 @@ package com.androiddevs.newsflash.repository -class NewsRepository \ No newline at end of file +import androidx.lifecycle.LiveData +import com.androiddevs.newsflash.data.NewsSources +import com.androiddevs.newsflash.network.ApiResponse +import com.androiddevs.newsflash.network.ServiceProvider + +class NewsRepository { + private val serviceProvider = ServiceProvider() + val newsService by lazy { + serviceProvider.provideNewsService() + } + fun getBusinessNews(): LiveData> { + return newsService.getTopHeadlines() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/view/fragments/BusinessHeadlineFragment.kt b/app/src/main/java/com/androiddevs/newsflash/view/fragments/BusinessHeadlineFragment.kt new file mode 100644 index 0000000..5078b64 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/view/fragments/BusinessHeadlineFragment.kt @@ -0,0 +1,20 @@ +package com.androiddevs.newsflash.view.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.androiddevs.newsflash.R + +class BusinessHeadlineFragment: Fragment() { + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + super.onCreateView(inflater, container, savedInstanceState) + return inflater.inflate(R.layout.fragment_business_headline, container, false) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/viewModel/BusinessHeadlineViewModel.kt b/app/src/main/java/com/androiddevs/newsflash/viewModel/BusinessHeadlineViewModel.kt new file mode 100644 index 0000000..1e93ed9 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/viewModel/BusinessHeadlineViewModel.kt @@ -0,0 +1,16 @@ +package com.androiddevs.newsflash.viewModel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel +import com.androiddevs.newsflash.data.NewsSources +import com.androiddevs.newsflash.network.ApiResponse +import com.androiddevs.newsflash.repository.NewsRepository + +class BusinessHeadlineViewModel: ViewModel() { + + private var newsRepository = NewsRepository() + + private fun getBusinessNews(): LiveData> { + newsRepository + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_business_headline.xml b/app/src/main/res/layout/fragment_business_headline.xml new file mode 100644 index 0000000..ced0fbd --- /dev/null +++ b/app/src/main/res/layout/fragment_business_headline.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index fb5ab35..faea9e4 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -1,13 +1,16 @@ + - + + \ No newline at end of file diff --git a/app/src/main/res/layout/home_news_row.xml b/app/src/main/res/layout/home_news_row.xml index a3351ff..c987187 100644 --- a/app/src/main/res/layout/home_news_row.xml +++ b/app/src/main/res/layout/home_news_row.xml @@ -17,7 +17,7 @@ android:layout_height="wrap_content"> + app:layout_constraintTop_toBottomOf="@+id/tv_title" /> + app:layout_constraintTop_toBottomOf="@+id/tv_author" /> diff --git a/app/src/main/res/layout/item_headlines.xml b/app/src/main/res/layout/item_headlines.xml new file mode 100644 index 0000000..bfc3589 --- /dev/null +++ b/app/src/main/res/layout/item_headlines.xml @@ -0,0 +1,31 @@ + + + + + + + + + \ No newline at end of file From 518be6501409972fb1a77d52d8c1b0a74da897aa Mon Sep 17 00:00:00 2001 From: sangeetha Date: Sun, 9 Aug 2020 11:15:34 +0530 Subject: [PATCH 04/15] Add API --- app/build.gradle | 12 ++++-- .../newsflash/constants/NewsConstants.kt | 3 +- .../newsflash/data/TopHeadlinesRequest.kt | 10 +++++ .../newsflash/network/NewsApiService.kt | 17 ++++---- .../newsflash/repository/NewsRepository.kt | 19 +++++++-- .../fragments/BusinessHeadlineFragment.kt | 20 ---------- .../newsflash/view/fragments/HomeFragment.kt | 39 ++++++++++++++++++- .../viewModel/BusinessHeadlineViewModel.kt | 16 -------- .../viewModel/HomeFragmentViewModel.kt | 14 ++++++- 9 files changed, 94 insertions(+), 56 deletions(-) create mode 100644 app/src/main/java/com/androiddevs/newsflash/data/TopHeadlinesRequest.kt delete mode 100644 app/src/main/java/com/androiddevs/newsflash/view/fragments/BusinessHeadlineFragment.kt delete mode 100644 app/src/main/java/com/androiddevs/newsflash/viewModel/BusinessHeadlineViewModel.kt diff --git a/app/build.gradle b/app/build.gradle index 9a77f47..b739181 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,12 +6,12 @@ apply plugin: 'kotlin-android-extensions' apply plugin: "kotlin-kapt" android { - compileSdkVersion 29 - buildToolsVersion "29.0.2" + compileSdkVersion 30 + buildToolsVersion "29.0.3" defaultConfig { applicationId "com.androiddevs.newsflash" minSdkVersion 21 - targetSdkVersion 29 + targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -38,6 +38,7 @@ dependencies { def rxAndroidVersion = "2.1.1" def okHttpVersion = "4.2.2" def navVersion = "2.1.0" + def lifecycle_version = "2.2.0" implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.1.0' @@ -75,4 +76,9 @@ dependencies { implementation 'com.google.dagger:dagger-android-support:2.23.2' kapt 'com.google.dagger:dagger-android-processor:2.23.2' kapt 'com.google.dagger:dagger-compiler:2.28' + + //VIEW-MODEL AND LIVE DATA + implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version" + kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" } diff --git a/app/src/main/java/com/androiddevs/newsflash/constants/NewsConstants.kt b/app/src/main/java/com/androiddevs/newsflash/constants/NewsConstants.kt index 3c2d7da..71185cd 100644 --- a/app/src/main/java/com/androiddevs/newsflash/constants/NewsConstants.kt +++ b/app/src/main/java/com/androiddevs/newsflash/constants/NewsConstants.kt @@ -1,3 +1,4 @@ package com.androiddevs.newsflash.constants -const val API_KEY = "83002035a125490c9b1cd28fedbd1945" \ No newline at end of file +const val API_KEY = "83002035a125490c9b1cd28fedbd1945" +const val SUCCESS_CODE = 200 \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/TopHeadlinesRequest.kt b/app/src/main/java/com/androiddevs/newsflash/data/TopHeadlinesRequest.kt new file mode 100644 index 0000000..75299ed --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/data/TopHeadlinesRequest.kt @@ -0,0 +1,10 @@ +package com.androiddevs.newsflash.data + +data class TopHeadlinesRequest ( + var country: String? = null, + var category: String? = null, + var sources: String? = null, + var keyword: String? = null, + var pageSize: Int = 0, + var page: Int = 0 +) \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/network/NewsApiService.kt b/app/src/main/java/com/androiddevs/newsflash/network/NewsApiService.kt index fe7d331..2543104 100644 --- a/app/src/main/java/com/androiddevs/newsflash/network/NewsApiService.kt +++ b/app/src/main/java/com/androiddevs/newsflash/network/NewsApiService.kt @@ -1,10 +1,10 @@ package com.androiddevs.newsflash.network - import androidx.lifecycle.LiveData import com.androiddevs.newsflash.data.NewsResult import com.androiddevs.newsflash.data.NewsSources import retrofit2.http.GET +import retrofit2.http.Path import retrofit2.http.Query interface NewsApiService { @@ -16,12 +16,13 @@ interface NewsApiService { @GET("top-headlines") fun getTopHeadlines( - @Query("country") country: String, - @Query("category") category: String, - @Query("sources") sources: String, - @Query("q") keyword: String, + @Query("country") country: String?, + @Query("category") category: String?, + @Query("sources") sources: String?, + @Query("q") keyword: String?, @Query("pageSize") pageSize: Int, - @Query("page") page: Int + @Query("page") page: Int, + @Query("apiKey") apiKey: String? ): LiveData> @GET("everything") @@ -39,8 +40,4 @@ interface NewsApiService { @Query("page") page: Int ): LiveData> - @GET("top-headlines") - fun getBusinessNews( - - ) } \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt b/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt index 5c5038e..0915157 100644 --- a/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt +++ b/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt @@ -1,16 +1,27 @@ package com.androiddevs.newsflash.repository import androidx.lifecycle.LiveData -import com.androiddevs.newsflash.data.NewsSources +import com.androiddevs.newsflash.constants.API_KEY +import com.androiddevs.newsflash.data.NewsResult +import com.androiddevs.newsflash.data.TopHeadlinesRequest import com.androiddevs.newsflash.network.ApiResponse import com.androiddevs.newsflash.network.ServiceProvider class NewsRepository { private val serviceProvider = ServiceProvider() - val newsService by lazy { + private val newsService by lazy { serviceProvider.provideNewsService() } - fun getBusinessNews(): LiveData> { - return newsService.getTopHeadlines() + + fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): LiveData> { + return newsService.getTopHeadlines( + topHeadlinesRequest.country, + topHeadlinesRequest.category, + topHeadlinesRequest.sources, + topHeadlinesRequest.keyword, + topHeadlinesRequest.pageSize, + topHeadlinesRequest.page, + API_KEY + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/view/fragments/BusinessHeadlineFragment.kt b/app/src/main/java/com/androiddevs/newsflash/view/fragments/BusinessHeadlineFragment.kt deleted file mode 100644 index 5078b64..0000000 --- a/app/src/main/java/com/androiddevs/newsflash/view/fragments/BusinessHeadlineFragment.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.androiddevs.newsflash.view.fragments - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import com.androiddevs.newsflash.R - -class BusinessHeadlineFragment: Fragment() { - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - super.onCreateView(inflater, container, savedInstanceState) - return inflater.inflate(R.layout.fragment_business_headline, container, false) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/view/fragments/HomeFragment.kt b/app/src/main/java/com/androiddevs/newsflash/view/fragments/HomeFragment.kt index da2b7f1..53b9f73 100644 --- a/app/src/main/java/com/androiddevs/newsflash/view/fragments/HomeFragment.kt +++ b/app/src/main/java/com/androiddevs/newsflash/view/fragments/HomeFragment.kt @@ -2,11 +2,48 @@ package com.androiddevs.newsflash.view.fragments import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider import com.androiddevs.newsflash.R +import com.androiddevs.newsflash.constants.SUCCESS_CODE +import com.androiddevs.newsflash.data.NewsResult +import com.androiddevs.newsflash.data.TopHeadlinesRequest +import com.androiddevs.newsflash.viewModel.HomeFragmentViewModel +class HomeFragment : Fragment() { -class HomeFragment : Fragment(R.layout.fragment_home) { + private val homeFragmentViewModel: HomeFragmentViewModel by lazy { + ViewModelProvider(this).get(HomeFragmentViewModel::class.java) + } + + private var articleList: List = listOf() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_business_headline, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + getTopHeadlinesFromNetwork() + } + + private fun getTopHeadlinesFromNetwork() { + homeFragmentViewModel.getTopHeadlines(TopHeadlinesRequest()).observe(this, Observer { apiResponse -> + if (!apiResponse.shouldShowRetryButton) { + apiResponse?.body?.let { + articleList = it.articles + } + } + }) + } } diff --git a/app/src/main/java/com/androiddevs/newsflash/viewModel/BusinessHeadlineViewModel.kt b/app/src/main/java/com/androiddevs/newsflash/viewModel/BusinessHeadlineViewModel.kt deleted file mode 100644 index 1e93ed9..0000000 --- a/app/src/main/java/com/androiddevs/newsflash/viewModel/BusinessHeadlineViewModel.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.androiddevs.newsflash.viewModel - -import androidx.lifecycle.LiveData -import androidx.lifecycle.ViewModel -import com.androiddevs.newsflash.data.NewsSources -import com.androiddevs.newsflash.network.ApiResponse -import com.androiddevs.newsflash.repository.NewsRepository - -class BusinessHeadlineViewModel: ViewModel() { - - private var newsRepository = NewsRepository() - - private fun getBusinessNews(): LiveData> { - newsRepository - } -} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt b/app/src/main/java/com/androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt index 35d3ee7..470fdd1 100644 --- a/app/src/main/java/com/androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt +++ b/app/src/main/java/com/androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt @@ -1,5 +1,17 @@ package com.androiddevs.newsflash.viewModel +import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel +import com.androiddevs.newsflash.data.NewsResult +import com.androiddevs.newsflash.data.TopHeadlinesRequest +import com.androiddevs.newsflash.network.ApiResponse +import com.androiddevs.newsflash.repository.NewsRepository -class HomeFragmentViewModel : ViewModel() \ No newline at end of file +class HomeFragmentViewModel : ViewModel() { + + private var newsRepository = NewsRepository() + + fun getTopHeadlines(topHeadlinesRequest: TopHeadlinesRequest): LiveData> { + return newsRepository.getBusinessNews(topHeadlinesRequest) + } +} \ No newline at end of file From a2be39ec125fdf79248adc5d3168d70804513d14 Mon Sep 17 00:00:00 2001 From: piyushpradeepkumar Date: Sat, 15 Aug 2020 22:20:42 +0530 Subject: [PATCH 05/15] Implemented dagger, coroutines cleaned up code and started writing tests --- app/build.gradle | 32 +++++------- app/src/main/AndroidManifest.xml | 3 +- .../newsflash/NewsFlashApplication.kt | 15 ++++++ .../newsflash/constants/NewsConstants.kt | 3 +- .../shared_preferences/PreferencesHelper.kt | 10 ++++ .../PreferencesHelperImpl.kt | 25 ++++++++++ .../newsflash/data/network/APILayer.kt | 32 ++++++++++++ .../{ => data}/network/NewsApiService.kt | 14 +++--- .../network/apiwrapper/RetrofitAPIWrapper.kt | 36 ++++++++++++++ .../data/network/contract/IAPILayer.kt | 10 ++++ .../network/models}/ApiResponse.kt | 2 +- .../data/{ => network/models}/NewsError.kt | 2 +- .../data/{ => network/models}/NewsResult.kt | 11 ++--- .../data/{ => network/models}/NewsSources.kt | 2 +- .../models}/TopHeadlinesRequest.kt | 2 +- .../data/repository/INewsRepository.kt | 7 +++ .../androiddevs/newsflash/di/CoreInjector.kt | 8 +++ .../newsflash/di/components/AppComponent.kt | 23 +++++++++ .../newsflash/di/modules/APIBinder.kt | 36 ++++++++++++++ .../newsflash/di/modules/CoreBinder.kt | 20 ++++++++ .../newsflash/di/modules/CoreModule.kt | 29 +++++++++++ .../newsflash/network/ApiClient.kt | 17 ------- .../newsflash/network/LiveDataCallAdapter.kt | 39 --------------- .../network/LiveDataCallAdapterFactory.kt | 30 ------------ .../newsflash/network/ServiceProvider.kt | 6 --- .../newsflash/repository/NewsRepository.kt | 27 ---------- .../newsflash/scope/ActivityScope.kt | 8 --- .../newsflash/scope/ApplicationScope.kt | 7 --- .../newsflash/{view => ui}/MainActivity.kt | 2 +- .../adapter/HomeNewsRecyelerAdapter.kt | 9 ++-- .../newsflash/ui/fragments/HomeFragment.kt | 30 ++++++++++++ .../ui/viewModel/HomeFragmentViewModel.kt | 17 +++++++ .../viewModeFactory/ViewModelFactory.kt} | 4 +- .../newsflash/utils/AppDispatchers.kt | 15 ++++++ .../newsflash/view/fragments/HomeFragment.kt | 49 ------------------- .../viewModel/HomeFragmentViewModel.kt | 17 ------- app/src/main/res/layout/activity_main.xml | 2 +- app/src/main/res/layout/fragment_home.xml | 2 +- app/src/main/res/layout/home_news_row.xml | 10 ++-- .../main/res/navigation/navigation_graph.xml | 2 +- .../androiddevs/newsflash/ExampleUnitTest.kt | 17 ------- .../ui/viewModel/HomeFragmentViewModelTest.kt | 26 ++++++++++ .../newsflash/utils/TestDispatcher.kt | 9 ++++ build.gradle | 4 +- 44 files changed, 396 insertions(+), 275 deletions(-) create mode 100644 app/src/main/java/com/androiddevs/newsflash/NewsFlashApplication.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/data/local/shared_preferences/PreferencesHelper.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/data/local/shared_preferences/PreferencesHelperImpl.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/data/network/APILayer.kt rename app/src/main/java/com/androiddevs/newsflash/{ => data}/network/NewsApiService.kt (76%) create mode 100644 app/src/main/java/com/androiddevs/newsflash/data/network/apiwrapper/RetrofitAPIWrapper.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/data/network/contract/IAPILayer.kt rename app/src/main/java/com/androiddevs/newsflash/{network => data/network/models}/ApiResponse.kt (94%) rename app/src/main/java/com/androiddevs/newsflash/data/{ => network/models}/NewsError.kt (74%) rename app/src/main/java/com/androiddevs/newsflash/data/{ => network/models}/NewsResult.kt (87%) rename app/src/main/java/com/androiddevs/newsflash/data/{ => network/models}/NewsSources.kt (89%) rename app/src/main/java/com/androiddevs/newsflash/data/{ => network/models}/TopHeadlinesRequest.kt (80%) create mode 100644 app/src/main/java/com/androiddevs/newsflash/data/repository/INewsRepository.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/di/CoreInjector.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/di/components/AppComponent.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/di/modules/APIBinder.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/di/modules/CoreBinder.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/di/modules/CoreModule.kt delete mode 100644 app/src/main/java/com/androiddevs/newsflash/network/ApiClient.kt delete mode 100644 app/src/main/java/com/androiddevs/newsflash/network/LiveDataCallAdapter.kt delete mode 100644 app/src/main/java/com/androiddevs/newsflash/network/LiveDataCallAdapterFactory.kt delete mode 100644 app/src/main/java/com/androiddevs/newsflash/network/ServiceProvider.kt delete mode 100644 app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt delete mode 100644 app/src/main/java/com/androiddevs/newsflash/scope/ActivityScope.kt delete mode 100644 app/src/main/java/com/androiddevs/newsflash/scope/ApplicationScope.kt rename app/src/main/java/com/androiddevs/newsflash/{view => ui}/MainActivity.kt (88%) rename app/src/main/java/com/androiddevs/newsflash/{view => ui}/adapter/HomeNewsRecyelerAdapter.kt (77%) create mode 100644 app/src/main/java/com/androiddevs/newsflash/ui/fragments/HomeFragment.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModel.kt rename app/src/main/java/com/androiddevs/newsflash/{viewModeFactory/ApplicationViewModelFactory.kt => ui/viewModel/viewModeFactory/ViewModelFactory.kt} (89%) create mode 100644 app/src/main/java/com/androiddevs/newsflash/utils/AppDispatchers.kt delete mode 100644 app/src/main/java/com/androiddevs/newsflash/view/fragments/HomeFragment.kt delete mode 100644 app/src/main/java/com/androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt delete mode 100644 app/src/test/java/com/androiddevs/newsflash/ExampleUnitTest.kt create mode 100644 app/src/test/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModelTest.kt create mode 100644 app/src/test/java/com/androiddevs/newsflash/utils/TestDispatcher.kt diff --git a/app/build.gradle b/app/build.gradle index b739181..57a41ba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,15 +34,14 @@ android { dependencies { - def retrofitVersion = "2.6.1" - def rxAndroidVersion = "2.1.1" - def okHttpVersion = "4.2.2" - def navVersion = "2.1.0" + def retrofitVersion = "2.9.0" + def okHttpVersion = "4.7.2" + def navVersion = "2.3.0" def lifecycle_version = "2.2.0" implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.core:core-ktx:1.1.0' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.core:core-ktx:1.3.1' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.cardview:cardview:1.0.0' @@ -53,13 +52,9 @@ dependencies { implementation "androidx.navigation:navigation-fragment-ktx:$navVersion" implementation "androidx.navigation:navigation-ui-ktx:$navVersion" - // RX ANDROID - implementation "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion" - - // RETROFIT - implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion" implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" - implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion" + implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // OK HTTP implementation "com.squareup.okhttp3:okhttp:$okHttpVersion" @@ -67,18 +62,17 @@ dependencies { // TESTING implementation 'androidx.legacy:legacy-support-v4:1.0.0' - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + testImplementation 'junit:junit:4.13' // DAGGER-ANDROID implementation 'com.google.dagger:dagger-android:2.23.2' implementation 'com.google.dagger:dagger-android-support:2.23.2' - kapt 'com.google.dagger:dagger-android-processor:2.23.2' + kapt 'com.google.dagger:dagger-android-processor:2.28' kapt 'com.google.dagger:dagger-compiler:2.28' //VIEW-MODEL AND LIVE DATA - implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" - implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version" - kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version" } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6e4903e..e8682dc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,8 +10,9 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" + android:name=".NewsFlashApplication" android:theme="@style/AppTheme"> - + diff --git a/app/src/main/java/com/androiddevs/newsflash/NewsFlashApplication.kt b/app/src/main/java/com/androiddevs/newsflash/NewsFlashApplication.kt new file mode 100644 index 0000000..3c65ab5 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/NewsFlashApplication.kt @@ -0,0 +1,15 @@ +package com.androiddevs.newsflash + +import android.app.Application +import com.androiddevs.newsflash.di.CoreInjector +import com.androiddevs.newsflash.di.components.DaggerAppComponent + + +class NewsFlashApplication : Application() { + + override fun onCreate() { + super.onCreate() + CoreInjector.injector = DaggerAppComponent.builder() + .application(this).build() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/constants/NewsConstants.kt b/app/src/main/java/com/androiddevs/newsflash/constants/NewsConstants.kt index 71185cd..3c2d7da 100644 --- a/app/src/main/java/com/androiddevs/newsflash/constants/NewsConstants.kt +++ b/app/src/main/java/com/androiddevs/newsflash/constants/NewsConstants.kt @@ -1,4 +1,3 @@ package com.androiddevs.newsflash.constants -const val API_KEY = "83002035a125490c9b1cd28fedbd1945" -const val SUCCESS_CODE = 200 \ No newline at end of file +const val API_KEY = "83002035a125490c9b1cd28fedbd1945" \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/local/shared_preferences/PreferencesHelper.kt b/app/src/main/java/com/androiddevs/newsflash/data/local/shared_preferences/PreferencesHelper.kt new file mode 100644 index 0000000..8264e59 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/data/local/shared_preferences/PreferencesHelper.kt @@ -0,0 +1,10 @@ +package com.androiddevs.newsflash.data.local.shared_preferences + +interface PreferencesHelper { + + fun setLoggedIn() + + fun logout() + + fun getLoggedIn():Boolean +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/local/shared_preferences/PreferencesHelperImpl.kt b/app/src/main/java/com/androiddevs/newsflash/data/local/shared_preferences/PreferencesHelperImpl.kt new file mode 100644 index 0000000..4826090 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/data/local/shared_preferences/PreferencesHelperImpl.kt @@ -0,0 +1,25 @@ +package com.androiddevs.newsflash.data.local.shared_preferences + +import android.content.SharedPreferences +import javax.inject.Inject + +class PreferencesHelperImpl @Inject constructor(private val sharedPreferences: SharedPreferences) : + PreferencesHelper { + + companion object { + const val LOGGEDIN = "LOGGED_IN" + } + + override fun setLoggedIn() { + sharedPreferences.edit().putBoolean(LOGGEDIN, true).apply() + } + + override fun logout() { + sharedPreferences.edit().clear().apply() + } + + override fun getLoggedIn(): Boolean { + return sharedPreferences.getBoolean(LOGGEDIN, false) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/network/APILayer.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/APILayer.kt new file mode 100644 index 0000000..dadcc22 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/APILayer.kt @@ -0,0 +1,32 @@ +package com.androiddevs.newsflash.data.network + +import com.androiddevs.newsflash.constants.API_KEY +import com.androiddevs.newsflash.data.network.apiwrapper.Response +import com.androiddevs.newsflash.data.network.apiwrapper.handleResponse +import com.androiddevs.newsflash.data.network.contract.IAPILayer +import com.androiddevs.newsflash.data.network.models.NewsResult +import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest +import retrofit2.Retrofit +import javax.inject.Inject + +class APILayer @Inject constructor(retrofit: Retrofit) : + IAPILayer { + + private val newsApiService by lazy { + retrofit.create(NewsApiService::class.java) + } + + override suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Response { + return handleResponse { + newsApiService.getTopHeadlines( + topHeadlinesRequest.country, + topHeadlinesRequest.category, + topHeadlinesRequest.sources, + topHeadlinesRequest.keyword, + topHeadlinesRequest.pageSize, + topHeadlinesRequest.page, + API_KEY + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/network/NewsApiService.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/NewsApiService.kt similarity index 76% rename from app/src/main/java/com/androiddevs/newsflash/network/NewsApiService.kt rename to app/src/main/java/com/androiddevs/newsflash/data/network/NewsApiService.kt index 2543104..0c50f4d 100644 --- a/app/src/main/java/com/androiddevs/newsflash/network/NewsApiService.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/NewsApiService.kt @@ -1,10 +1,8 @@ -package com.androiddevs.newsflash.network +package com.androiddevs.newsflash.data.network -import androidx.lifecycle.LiveData -import com.androiddevs.newsflash.data.NewsResult -import com.androiddevs.newsflash.data.NewsSources +import com.androiddevs.newsflash.data.network.models.NewsResult +import com.androiddevs.newsflash.data.network.models.NewsSources import retrofit2.http.GET -import retrofit2.http.Path import retrofit2.http.Query interface NewsApiService { @@ -12,7 +10,7 @@ interface NewsApiService { fun getNewsSources( @Query("category") category: String, @Query("language") language: String, @Query("country") country: String - ): LiveData> + ): NewsSources.Sources @GET("top-headlines") fun getTopHeadlines( @@ -23,7 +21,7 @@ interface NewsApiService { @Query("pageSize") pageSize: Int, @Query("page") page: Int, @Query("apiKey") apiKey: String? - ): LiveData> + ): NewsResult.News @GET("everything") fun getEverything( @@ -38,6 +36,6 @@ interface NewsApiService { @Query("sortBy") sortBy: String, @Query("pageSize") pageSize: Int, @Query("page") page: Int - ): LiveData> + ): NewsResult.News } \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/network/apiwrapper/RetrofitAPIWrapper.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/apiwrapper/RetrofitAPIWrapper.kt new file mode 100644 index 0000000..e549b42 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/apiwrapper/RetrofitAPIWrapper.kt @@ -0,0 +1,36 @@ +package com.androiddevs.newsflash.data.network.apiwrapper + +suspend fun handleResponse(apiBlock: suspend () -> T): Response { + return try { + val response = apiBlock() + if (response != null) { + Response.success(response) + } else { + Response.error("Something went wrong. Please try again later") + } + } catch (exception: java.lang.Exception) { + Response.error("Something went wrong. Please try again later", exception) + } +} + +data class Response( + val status: Status, + val data: T? = null, + val message: String? = null, + val errorException: Exception? = null +) { + + companion object { + fun success(data: T?): Response { + return Response(Status.SUCCESS, data) + } + + fun error(errorMessage: String? = null, exception: Exception? = null): Response { + return Response(Status.ERROR, message = errorMessage, errorException = exception) + } + } +} + +enum class Status { + SUCCESS, ERROR +} diff --git a/app/src/main/java/com/androiddevs/newsflash/data/network/contract/IAPILayer.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/contract/IAPILayer.kt new file mode 100644 index 0000000..2990c74 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/contract/IAPILayer.kt @@ -0,0 +1,10 @@ +package com.androiddevs.newsflash.data.network.contract + +import com.androiddevs.newsflash.data.network.apiwrapper.Response +import com.androiddevs.newsflash.data.network.models.NewsResult +import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest + +interface IAPILayer { + + suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Response +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/network/ApiResponse.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/models/ApiResponse.kt similarity index 94% rename from app/src/main/java/com/androiddevs/newsflash/network/ApiResponse.kt rename to app/src/main/java/com/androiddevs/newsflash/data/network/models/ApiResponse.kt index 3a80276..93a6c79 100644 --- a/app/src/main/java/com/androiddevs/newsflash/network/ApiResponse.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/models/ApiResponse.kt @@ -1,4 +1,4 @@ -package com.androiddevs.newsflash.network +package com.androiddevs.newsflash.data.network.models import retrofit2.Call import retrofit2.Response diff --git a/app/src/main/java/com/androiddevs/newsflash/data/NewsError.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsError.kt similarity index 74% rename from app/src/main/java/com/androiddevs/newsflash/data/NewsError.kt rename to app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsError.kt index 60dd15d..44b9249 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/NewsError.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsError.kt @@ -1,4 +1,4 @@ -package com.androiddevs.newsflash.data +package com.androiddevs.newsflash.data.network.models object NewsError { data class Error( diff --git a/app/src/main/java/com/androiddevs/newsflash/data/NewsResult.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsResult.kt similarity index 87% rename from app/src/main/java/com/androiddevs/newsflash/data/NewsResult.kt rename to app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsResult.kt index cf7016e..33d9ffe 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/NewsResult.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsResult.kt @@ -1,14 +1,12 @@ -package com.androiddevs.newsflash.data +package com.androiddevs.newsflash.data.network.models -object NewsResult -{ +object NewsResult { data class News( val status: String? = null, val totalResults: Int = 0, val articles: List
= listOf() - ) - { + ) { data class Article( val source: Source? = null, val author: String? = null, @@ -18,8 +16,7 @@ object NewsResult val urlToImage: String? = null, val publishedAt: String? = null, val content: String? = null - ) - { + ) { data class Source( val id: String? = null, val name: String? = null diff --git a/app/src/main/java/com/androiddevs/newsflash/data/NewsSources.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsSources.kt similarity index 89% rename from app/src/main/java/com/androiddevs/newsflash/data/NewsSources.kt rename to app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsSources.kt index 23f91c8..3ab4e49 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/NewsSources.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsSources.kt @@ -1,4 +1,4 @@ -package com.androiddevs.newsflash.data +package com.androiddevs.newsflash.data.network.models object NewsSources { diff --git a/app/src/main/java/com/androiddevs/newsflash/data/TopHeadlinesRequest.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/models/TopHeadlinesRequest.kt similarity index 80% rename from app/src/main/java/com/androiddevs/newsflash/data/TopHeadlinesRequest.kt rename to app/src/main/java/com/androiddevs/newsflash/data/network/models/TopHeadlinesRequest.kt index 75299ed..a89a1a8 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/TopHeadlinesRequest.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/models/TopHeadlinesRequest.kt @@ -1,4 +1,4 @@ -package com.androiddevs.newsflash.data +package com.androiddevs.newsflash.data.network.models data class TopHeadlinesRequest ( var country: String? = null, diff --git a/app/src/main/java/com/androiddevs/newsflash/data/repository/INewsRepository.kt b/app/src/main/java/com/androiddevs/newsflash/data/repository/INewsRepository.kt new file mode 100644 index 0000000..6ae57dc --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/data/repository/INewsRepository.kt @@ -0,0 +1,7 @@ +package com.androiddevs.newsflash.data.repository + +import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest + +interface INewsRepository { + fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest) +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/di/CoreInjector.kt b/app/src/main/java/com/androiddevs/newsflash/di/CoreInjector.kt new file mode 100644 index 0000000..656f604 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/di/CoreInjector.kt @@ -0,0 +1,8 @@ +package com.androiddevs.newsflash.di + +import com.androiddevs.newsflash.di.components.AppComponent + +object CoreInjector { + @JvmStatic + lateinit var injector: AppComponent +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/di/components/AppComponent.kt b/app/src/main/java/com/androiddevs/newsflash/di/components/AppComponent.kt new file mode 100644 index 0000000..33a8fea --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/di/components/AppComponent.kt @@ -0,0 +1,23 @@ +package com.androiddevs.newsflash.di.components + +import android.app.Application +import com.androiddevs.newsflash.di.modules.CoreBinder +import com.androiddevs.newsflash.di.modules.CoreModule +import dagger.BindsInstance +import dagger.Component +import javax.inject.Singleton + + +@Singleton +@Component(modules = [CoreBinder::class, CoreModule::class]) +interface AppComponent { + + @Component.Builder + interface Builder { + + @BindsInstance + fun application(application: Application): Builder + + fun build(): AppComponent + } +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/di/modules/APIBinder.kt b/app/src/main/java/com/androiddevs/newsflash/di/modules/APIBinder.kt new file mode 100644 index 0000000..0a8fd8b --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/di/modules/APIBinder.kt @@ -0,0 +1,36 @@ +package com.androiddevs.newsflash.di.modules + +import com.androiddevs.newsflash.data.network.APILayer +import com.androiddevs.newsflash.data.network.contract.IAPILayer +import dagger.Binds +import dagger.Module +import dagger.Provides +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + + +@Module(includes = [APIBinder.APIModule::class]) +abstract class APIBinder { + + companion object { + const val PREF_NAME = "com.androiddevs.newsflash" + const val BASE_URL = "https://newsapi.org/v2/" + } + + @Binds + abstract fun bindsAPILayer(apiLayer: APILayer): IAPILayer + + @Module + internal object APIModule { + @Provides + fun provideRetrofitInstance(gsonConverterFactory: GsonConverterFactory): Retrofit = + Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(gsonConverterFactory) + .build() + + + @Provides + fun providesGsonConverterFactory(): GsonConverterFactory = GsonConverterFactory.create() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/di/modules/CoreBinder.kt b/app/src/main/java/com/androiddevs/newsflash/di/modules/CoreBinder.kt new file mode 100644 index 0000000..3912ac9 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/di/modules/CoreBinder.kt @@ -0,0 +1,20 @@ +package com.androiddevs.newsflash.di.modules + +import com.androiddevs.newsflash.data.local.shared_preferences.PreferencesHelper +import com.androiddevs.newsflash.data.local.shared_preferences.PreferencesHelperImpl +import com.androiddevs.newsflash.utils.AppDispatchers +import com.androiddevs.newsflash.utils.IDispatchers +import dagger.Binds +import dagger.Module +import dagger.Provides + + +@Module +abstract class CoreBinder { + + @Binds + abstract fun bindsSharedPreferenceHelper(preferencesHelperImpl: PreferencesHelperImpl): PreferencesHelper + + @Binds + abstract fun bindsDispatchers(appDispatchers: AppDispatchers): IDispatchers +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/di/modules/CoreModule.kt b/app/src/main/java/com/androiddevs/newsflash/di/modules/CoreModule.kt new file mode 100644 index 0000000..632ed6f --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/di/modules/CoreModule.kt @@ -0,0 +1,29 @@ +package com.androiddevs.newsflash.di.modules + +import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import com.androiddevs.newsflash.utils.AppDispatchers +import com.androiddevs.newsflash.utils.IDispatchers +import dagger.Module +import dagger.Provides +import javax.inject.Singleton + + +@Module(includes = [APIBinder::class]) +class CoreModule { + + companion object { + const val PREF_NAME = "com.androiddevs.newsflash" + const val BASE_URL = "com.androiddevs.newsflash" + } + + @Singleton + @Provides + fun providesSharedPreferences(context: Application): SharedPreferences = + context.applicationContext.getSharedPreferences( + PREF_NAME, + Context.MODE_PRIVATE + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/network/ApiClient.kt b/app/src/main/java/com/androiddevs/newsflash/network/ApiClient.kt deleted file mode 100644 index 169e61a..0000000 --- a/app/src/main/java/com/androiddevs/newsflash/network/ApiClient.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.androiddevs.newsflash.network - -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory - -object ApiClient { - private val mRetrofitClient: Retrofit by lazy { - Retrofit.Builder() - .addCallAdapterFactory(LiveDataCallAdapterFactory()) - .addConverterFactory(GsonConverterFactory.create()) - .baseUrl("https://newsapi.org/v2/") - .build() - } - - fun getNetworkClient(): Retrofit = mRetrofitClient - -} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/network/LiveDataCallAdapter.kt b/app/src/main/java/com/androiddevs/newsflash/network/LiveDataCallAdapter.kt deleted file mode 100644 index a6263d8..0000000 --- a/app/src/main/java/com/androiddevs/newsflash/network/LiveDataCallAdapter.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.androiddevs.newsflash.network - -import androidx.lifecycle.LiveData -import retrofit2.Call -import retrofit2.CallAdapter -import retrofit2.Callback -import retrofit2.Response -import java.lang.reflect.Type -import java.util.concurrent.atomic.AtomicBoolean - -class LiveDataCallAdapter(private var responseType: Type) : CallAdapter>> { - - override fun responseType(): Type { - return responseType - } - - override fun adapt(call: Call): LiveData> { - return object : LiveData>() { - var started = AtomicBoolean(false) - override fun onActive() { - super.onActive() - if (started.compareAndSet(false, true)) { - call.enqueue(object : Callback { - override fun onResponse(call: Call, - response: Response - ) { - postValue(ApiResponse(response, call)) - } - - override fun onFailure(call: Call, throwable: Throwable) { - postValue(ApiResponse(throwable, call)) - } - }) - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/network/LiveDataCallAdapterFactory.kt b/app/src/main/java/com/androiddevs/newsflash/network/LiveDataCallAdapterFactory.kt deleted file mode 100644 index 4fdc49e..0000000 --- a/app/src/main/java/com/androiddevs/newsflash/network/LiveDataCallAdapterFactory.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.androiddevs.newsflash.network - -import androidx.lifecycle.LiveData -import retrofit2.CallAdapter -import retrofit2.Retrofit -import java.lang.reflect.ParameterizedType -import java.lang.reflect.Type - -class LiveDataCallAdapterFactory : CallAdapter.Factory() { - - override fun get(returnType: Type, annotations: Array?, - retrofit: Retrofit?): CallAdapter<*, *>? { - if (CallAdapter.Factory.getRawType(returnType) != LiveData::class.java) { - return null - } - - val observableType = CallAdapter.Factory.getParameterUpperBound(0, returnType as ParameterizedType) - val rawObservableType = CallAdapter.Factory.getRawType(observableType) - - if (rawObservableType != ApiResponse::class.java) { - throw IllegalArgumentException("type must be a resource") - } - if (observableType !is ParameterizedType) { - throw IllegalArgumentException("resource must be parameterized") - } - - val bodyType = CallAdapter.Factory.getParameterUpperBound(0, observableType) - return LiveDataCallAdapter(bodyType) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/network/ServiceProvider.kt b/app/src/main/java/com/androiddevs/newsflash/network/ServiceProvider.kt deleted file mode 100644 index 1513aa6..0000000 --- a/app/src/main/java/com/androiddevs/newsflash/network/ServiceProvider.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.androiddevs.newsflash.network - -class ServiceProvider { - - fun provideNewsService(): NewsApiService = ApiClient.getNetworkClient().create(NewsApiService::class.java) -} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt b/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt deleted file mode 100644 index 0915157..0000000 --- a/app/src/main/java/com/androiddevs/newsflash/repository/NewsRepository.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.androiddevs.newsflash.repository - -import androidx.lifecycle.LiveData -import com.androiddevs.newsflash.constants.API_KEY -import com.androiddevs.newsflash.data.NewsResult -import com.androiddevs.newsflash.data.TopHeadlinesRequest -import com.androiddevs.newsflash.network.ApiResponse -import com.androiddevs.newsflash.network.ServiceProvider - -class NewsRepository { - private val serviceProvider = ServiceProvider() - private val newsService by lazy { - serviceProvider.provideNewsService() - } - - fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): LiveData> { - return newsService.getTopHeadlines( - topHeadlinesRequest.country, - topHeadlinesRequest.category, - topHeadlinesRequest.sources, - topHeadlinesRequest.keyword, - topHeadlinesRequest.pageSize, - topHeadlinesRequest.page, - API_KEY - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/scope/ActivityScope.kt b/app/src/main/java/com/androiddevs/newsflash/scope/ActivityScope.kt deleted file mode 100644 index 5b5a0a6..0000000 --- a/app/src/main/java/com/androiddevs/newsflash/scope/ActivityScope.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.androiddevs.newsflash.scope - -import javax.inject.Scope - -@MustBeDocumented -@Scope -@Retention(AnnotationRetention.RUNTIME) -annotation class ActivityScope \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/scope/ApplicationScope.kt b/app/src/main/java/com/androiddevs/newsflash/scope/ApplicationScope.kt deleted file mode 100644 index 90fb894..0000000 --- a/app/src/main/java/com/androiddevs/newsflash/scope/ApplicationScope.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.androiddevs.newsflash.scope - -import javax.inject.Scope - -@Scope -@Retention(AnnotationRetention.RUNTIME) -annotation class ApplicationScope \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/view/MainActivity.kt b/app/src/main/java/com/androiddevs/newsflash/ui/MainActivity.kt similarity index 88% rename from app/src/main/java/com/androiddevs/newsflash/view/MainActivity.kt rename to app/src/main/java/com/androiddevs/newsflash/ui/MainActivity.kt index 6fc3760..798e889 100644 --- a/app/src/main/java/com/androiddevs/newsflash/view/MainActivity.kt +++ b/app/src/main/java/com/androiddevs/newsflash/ui/MainActivity.kt @@ -1,4 +1,4 @@ -package com.androiddevs.newsflash.view +package com.androiddevs.newsflash.ui import androidx.appcompat.app.AppCompatActivity import android.os.Bundle diff --git a/app/src/main/java/com/androiddevs/newsflash/view/adapter/HomeNewsRecyelerAdapter.kt b/app/src/main/java/com/androiddevs/newsflash/ui/adapter/HomeNewsRecyelerAdapter.kt similarity index 77% rename from app/src/main/java/com/androiddevs/newsflash/view/adapter/HomeNewsRecyelerAdapter.kt rename to app/src/main/java/com/androiddevs/newsflash/ui/adapter/HomeNewsRecyelerAdapter.kt index c916382..e91c9b5 100644 --- a/app/src/main/java/com/androiddevs/newsflash/view/adapter/HomeNewsRecyelerAdapter.kt +++ b/app/src/main/java/com/androiddevs/newsflash/ui/adapter/HomeNewsRecyelerAdapter.kt @@ -1,4 +1,4 @@ -package com.androiddevs.newsflash.view.adapter +package com.androiddevs.newsflash.ui.adapter import android.view.LayoutInflater @@ -6,7 +6,7 @@ import android.view.ViewGroup import androidx.databinding.DataBindingUtil import androidx.recyclerview.widget.RecyclerView import com.androiddevs.newsflash.R -import com.androiddevs.newsflash.data.NewsResult +import com.androiddevs.newsflash.data.network.models.NewsResult import com.androiddevs.newsflash.databinding.HomeNewsRowBinding @@ -33,11 +33,10 @@ class HomeNewsRecyclerAdapter(private var newsList: ArrayList = + MutableLiveData(HomeScreenStates.Loading) + + fun subscribeToUIState(): LiveData = screenStates +} + +sealed class HomeScreenStates { + object Loading : HomeScreenStates() +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/viewModeFactory/ApplicationViewModelFactory.kt b/app/src/main/java/com/androiddevs/newsflash/ui/viewModel/viewModeFactory/ViewModelFactory.kt similarity index 89% rename from app/src/main/java/com/androiddevs/newsflash/viewModeFactory/ApplicationViewModelFactory.kt rename to app/src/main/java/com/androiddevs/newsflash/ui/viewModel/viewModeFactory/ViewModelFactory.kt index 0694abb..c9abec7 100644 --- a/app/src/main/java/com/androiddevs/newsflash/viewModeFactory/ApplicationViewModelFactory.kt +++ b/app/src/main/java/com/androiddevs/newsflash/ui/viewModel/viewModeFactory/ViewModelFactory.kt @@ -1,4 +1,4 @@ -package com.androiddevs.newsflash.viewModeFactory +package com.androiddevs.newsflash.ui.viewModel.viewModeFactory import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider @@ -6,7 +6,7 @@ import javax.inject.Inject import javax.inject.Provider @Suppress("UNCHECKED_CAST") -class ApplicationViewModelFactory +class ViewModelFactory @Inject constructor( private val creators: Map, @JvmSuppressWildcards Provider> ) : ViewModelProvider.Factory { diff --git a/app/src/main/java/com/androiddevs/newsflash/utils/AppDispatchers.kt b/app/src/main/java/com/androiddevs/newsflash/utils/AppDispatchers.kt new file mode 100644 index 0000000..6f7e58f --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/utils/AppDispatchers.kt @@ -0,0 +1,15 @@ +package com.androiddevs.newsflash.utils + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import javax.inject.Inject + +class AppDispatchers @Inject constructor() : IDispatchers { + override val mainDispatchers: CoroutineDispatcher = Dispatchers.Main + override val ioDispatcher: CoroutineDispatcher = Dispatchers.IO +} + +interface IDispatchers { + val mainDispatchers: CoroutineDispatcher + val ioDispatcher: CoroutineDispatcher +} diff --git a/app/src/main/java/com/androiddevs/newsflash/view/fragments/HomeFragment.kt b/app/src/main/java/com/androiddevs/newsflash/view/fragments/HomeFragment.kt deleted file mode 100644 index 53b9f73..0000000 --- a/app/src/main/java/com/androiddevs/newsflash/view/fragments/HomeFragment.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.androiddevs.newsflash.view.fragments - - -import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProvider -import com.androiddevs.newsflash.R -import com.androiddevs.newsflash.constants.SUCCESS_CODE -import com.androiddevs.newsflash.data.NewsResult -import com.androiddevs.newsflash.data.TopHeadlinesRequest -import com.androiddevs.newsflash.viewModel.HomeFragmentViewModel - -class HomeFragment : Fragment() { - - private val homeFragmentViewModel: HomeFragmentViewModel by lazy { - ViewModelProvider(this).get(HomeFragmentViewModel::class.java) - } - - private var articleList: List = listOf() - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_business_headline, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - getTopHeadlinesFromNetwork() - } - - private fun getTopHeadlinesFromNetwork() { - homeFragmentViewModel.getTopHeadlines(TopHeadlinesRequest()).observe(this, Observer { apiResponse -> - if (!apiResponse.shouldShowRetryButton) { - apiResponse?.body?.let { - articleList = it.articles - } - } - }) - } - -} diff --git a/app/src/main/java/com/androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt b/app/src/main/java/com/androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt deleted file mode 100644 index 470fdd1..0000000 --- a/app/src/main/java/com/androiddevs/newsflash/viewModel/HomeFragmentViewModel.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.androiddevs.newsflash.viewModel - -import androidx.lifecycle.LiveData -import androidx.lifecycle.ViewModel -import com.androiddevs.newsflash.data.NewsResult -import com.androiddevs.newsflash.data.TopHeadlinesRequest -import com.androiddevs.newsflash.network.ApiResponse -import com.androiddevs.newsflash.repository.NewsRepository - -class HomeFragmentViewModel : ViewModel() { - - private var newsRepository = NewsRepository() - - fun getTopHeadlines(topHeadlinesRequest: TopHeadlinesRequest): LiveData> { - return newsRepository.getBusinessNews(topHeadlinesRequest) - } -} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 3c27338..ef2cafa 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -5,7 +5,7 @@ + tools:context=".ui.MainActivity"> + tools:context=".ui.fragments.HomeFragment"> + type="com.androiddevs.newsflash.data.network.models.NewsResult" /> + + + + diff --git a/app/src/main/res/navigation/navigation_graph.xml b/app/src/main/res/navigation/navigation_graph.xml index 64fcaff..9868ab1 100644 --- a/app/src/main/res/navigation/navigation_graph.xml +++ b/app/src/main/res/navigation/navigation_graph.xml @@ -7,7 +7,7 @@ \ No newline at end of file diff --git a/app/src/test/java/com/androiddevs/newsflash/ExampleUnitTest.kt b/app/src/test/java/com/androiddevs/newsflash/ExampleUnitTest.kt deleted file mode 100644 index 192d156..0000000 --- a/app/src/test/java/com/androiddevs/newsflash/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.androiddevs.newsflash - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/app/src/test/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModelTest.kt b/app/src/test/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModelTest.kt new file mode 100644 index 0000000..19995d6 --- /dev/null +++ b/app/src/test/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModelTest.kt @@ -0,0 +1,26 @@ +package com.androiddevs.newsflash.ui.viewModel + +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + + +class HomeFragmentViewModelTest { + + private val homeFragmentViewModel by lazy { HomeFragmentViewModel() } + + @Before + fun setUp() { + + } + + @Test + fun `when page is created,default state should be loading`() { + assertEquals(HomeScreenStates.Loading, homeFragmentViewModel.subscribeToUIState().value) + } + + @Test + fun `when page is created, then the top news headlines must be retrieved`() { + + } +} \ No newline at end of file diff --git a/app/src/test/java/com/androiddevs/newsflash/utils/TestDispatcher.kt b/app/src/test/java/com/androiddevs/newsflash/utils/TestDispatcher.kt new file mode 100644 index 0000000..518e3fd --- /dev/null +++ b/app/src/test/java/com/androiddevs/newsflash/utils/TestDispatcher.kt @@ -0,0 +1,9 @@ +package com.androiddevs.newsflash.utils + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +class TestDispatcher : IDispatchers { + override val mainDispatchers: CoroutineDispatcher = Dispatchers.Default + override val ioDispatcher: CoroutineDispatcher = Dispatchers.Default +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index e3245e7..945135a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.3.72' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.1' + classpath 'com.android.tools.build:gradle:3.5.4' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 0e20081a1e6f3405e4cf8ef2bec82db2144cf5f2 Mon Sep 17 00:00:00 2001 From: piyushpradeepkumar Date: Sun, 16 Aug 2020 22:46:26 +0530 Subject: [PATCH 06/15] added fake implementations and more tests --- app/build.gradle | 31 +- .../{APILayer.kt => NewsAPILayerImpl.kt} | 6 +- .../{IAPILayer.kt => NewsAPILayer.kt} | 3 +- .../network/models/TopHeadlinesRequest.kt | 9 +- .../data/repository/INewsRepository.kt | 7 - .../data/repository/NewsRepositoryImpl.kt | 15 + .../repository/contract/NewsRepository.kt | 9 + .../newsflash/di/modules/APIBinder.kt | 6 +- .../newsflash/di/modules/CoreBinder.kt | 10 +- .../newsflash/di/modules/CoreModule.kt | 2 - .../ui/viewModel/HomeFragmentViewModel.kt | 35 ++- .../newsflash/utils/AppDispatchers.kt | 11 +- .../data/repository/FakeNewsRepositoryImpl.kt | 34 +++ .../di/components/FakeAppComponent.kt | 16 ++ .../newsflash/di/modules/FakeAppBinders.kt | 24 ++ .../ui/viewModel/HomeFragmentViewModelTest.kt | 61 +++- .../newsflash/utils/TestDispatcher.kt | 12 +- .../androiddevs/newsflash/utils/Utility.kt | 30 ++ .../success_response.json | 266 ++++++++++++++++++ build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- 21 files changed, 545 insertions(+), 48 deletions(-) rename app/src/main/java/com/androiddevs/newsflash/data/network/{APILayer.kt => NewsAPILayerImpl.kt} (87%) rename app/src/main/java/com/androiddevs/newsflash/data/network/contract/{IAPILayer.kt => NewsAPILayer.kt} (93%) delete mode 100644 app/src/main/java/com/androiddevs/newsflash/data/repository/INewsRepository.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/data/repository/NewsRepositoryImpl.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/data/repository/contract/NewsRepository.kt create mode 100644 app/src/test/java/com/androiddevs/newsflash/data/repository/FakeNewsRepositoryImpl.kt create mode 100644 app/src/test/java/com/androiddevs/newsflash/di/components/FakeAppComponent.kt create mode 100644 app/src/test/java/com/androiddevs/newsflash/di/modules/FakeAppBinders.kt create mode 100644 app/src/test/java/com/androiddevs/newsflash/utils/Utility.kt create mode 100644 app/src/test/resources/news_top_headline_response/success_response.json diff --git a/app/build.gradle b/app/build.gradle index 57a41ba..500ef90 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,7 +17,9 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" buildConfigField("String", "API_KEY", API_KEY) } - + testOptions { + unitTests.returnDefaultValues = true + } buildTypes { release { @@ -25,10 +27,20 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } - + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } dataBinding { enabled = true } + sourceSets { + debug { + assets { + srcDirs 'src/debug/assets' + } + } + } } @@ -63,16 +75,23 @@ dependencies { // TESTING implementation 'androidx.legacy:legacy-support-v4:1.0.0' testImplementation 'junit:junit:4.13' + testImplementation 'org.mockito:mockito-core:3.0.0' + testImplementation "androidx.arch.core:core-testing:2.1.0" - // DAGGER-ANDROID - implementation 'com.google.dagger:dagger-android:2.23.2' - implementation 'com.google.dagger:dagger-android-support:2.23.2' - kapt 'com.google.dagger:dagger-android-processor:2.28' + // DAGGER + implementation 'com.google.dagger:dagger:2.28' kapt 'com.google.dagger:dagger-compiler:2.28' + kaptTest 'com.google.dagger:dagger-compiler:2.28' + annotationProcessor 'com.google.dagger:dagger-compiler:2.28' + //VIEW-MODEL AND LIVE DATA implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version" + + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.8" } diff --git a/app/src/main/java/com/androiddevs/newsflash/data/network/APILayer.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/NewsAPILayerImpl.kt similarity index 87% rename from app/src/main/java/com/androiddevs/newsflash/data/network/APILayer.kt rename to app/src/main/java/com/androiddevs/newsflash/data/network/NewsAPILayerImpl.kt index dadcc22..f357d85 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/network/APILayer.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/NewsAPILayerImpl.kt @@ -3,14 +3,14 @@ package com.androiddevs.newsflash.data.network import com.androiddevs.newsflash.constants.API_KEY import com.androiddevs.newsflash.data.network.apiwrapper.Response import com.androiddevs.newsflash.data.network.apiwrapper.handleResponse -import com.androiddevs.newsflash.data.network.contract.IAPILayer +import com.androiddevs.newsflash.data.network.contract.NewsAPILayer import com.androiddevs.newsflash.data.network.models.NewsResult import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest import retrofit2.Retrofit import javax.inject.Inject -class APILayer @Inject constructor(retrofit: Retrofit) : - IAPILayer { +class NewsAPILayerImpl @Inject constructor(retrofit: Retrofit) : + NewsAPILayer { private val newsApiService by lazy { retrofit.create(NewsApiService::class.java) diff --git a/app/src/main/java/com/androiddevs/newsflash/data/network/contract/IAPILayer.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/contract/NewsAPILayer.kt similarity index 93% rename from app/src/main/java/com/androiddevs/newsflash/data/network/contract/IAPILayer.kt rename to app/src/main/java/com/androiddevs/newsflash/data/network/contract/NewsAPILayer.kt index 2990c74..88cd290 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/network/contract/IAPILayer.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/contract/NewsAPILayer.kt @@ -4,7 +4,6 @@ import com.androiddevs.newsflash.data.network.apiwrapper.Response import com.androiddevs.newsflash.data.network.models.NewsResult import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest -interface IAPILayer { - +interface NewsAPILayer { suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Response } \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/network/models/TopHeadlinesRequest.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/models/TopHeadlinesRequest.kt index a89a1a8..683dd07 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/network/models/TopHeadlinesRequest.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/models/TopHeadlinesRequest.kt @@ -1,10 +1,15 @@ package com.androiddevs.newsflash.data.network.models -data class TopHeadlinesRequest ( +data class TopHeadlinesRequest( var country: String? = null, var category: String? = null, var sources: String? = null, var keyword: String? = null, var pageSize: Int = 0, var page: Int = 0 -) \ No newline at end of file +) { + companion object { + fun getDefaultParams(): TopHeadlinesRequest = + TopHeadlinesRequest("in", pageSize = 10, page = 0) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/repository/INewsRepository.kt b/app/src/main/java/com/androiddevs/newsflash/data/repository/INewsRepository.kt deleted file mode 100644 index 6ae57dc..0000000 --- a/app/src/main/java/com/androiddevs/newsflash/data/repository/INewsRepository.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.androiddevs.newsflash.data.repository - -import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest - -interface INewsRepository { - fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest) -} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/repository/NewsRepositoryImpl.kt b/app/src/main/java/com/androiddevs/newsflash/data/repository/NewsRepositoryImpl.kt new file mode 100644 index 0000000..db0d22f --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/data/repository/NewsRepositoryImpl.kt @@ -0,0 +1,15 @@ +package com.androiddevs.newsflash.data.repository + +import com.androiddevs.newsflash.data.network.apiwrapper.Response +import com.androiddevs.newsflash.data.network.contract.NewsAPILayer +import com.androiddevs.newsflash.data.network.models.NewsResult +import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest +import com.androiddevs.newsflash.data.repository.contract.NewsRepository +import javax.inject.Inject + +class NewsRepositoryImpl @Inject constructor(private val apiLayer: NewsAPILayer) : + NewsRepository { + override suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Response { + return apiLayer.getBusinessNews(topHeadlinesRequest) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/repository/contract/NewsRepository.kt b/app/src/main/java/com/androiddevs/newsflash/data/repository/contract/NewsRepository.kt new file mode 100644 index 0000000..ef12e65 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/data/repository/contract/NewsRepository.kt @@ -0,0 +1,9 @@ +package com.androiddevs.newsflash.data.repository.contract + +import com.androiddevs.newsflash.data.network.apiwrapper.Response +import com.androiddevs.newsflash.data.network.models.NewsResult +import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest + +interface NewsRepository { + suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Response +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/di/modules/APIBinder.kt b/app/src/main/java/com/androiddevs/newsflash/di/modules/APIBinder.kt index 0a8fd8b..c2b2c0c 100644 --- a/app/src/main/java/com/androiddevs/newsflash/di/modules/APIBinder.kt +++ b/app/src/main/java/com/androiddevs/newsflash/di/modules/APIBinder.kt @@ -1,7 +1,7 @@ package com.androiddevs.newsflash.di.modules -import com.androiddevs.newsflash.data.network.APILayer -import com.androiddevs.newsflash.data.network.contract.IAPILayer +import com.androiddevs.newsflash.data.network.NewsAPILayerImpl +import com.androiddevs.newsflash.data.network.contract.NewsAPILayer import dagger.Binds import dagger.Module import dagger.Provides @@ -18,7 +18,7 @@ abstract class APIBinder { } @Binds - abstract fun bindsAPILayer(apiLayer: APILayer): IAPILayer + abstract fun bindsAPILayer(newsApiLayerImpl: NewsAPILayerImpl): NewsAPILayer @Module internal object APIModule { diff --git a/app/src/main/java/com/androiddevs/newsflash/di/modules/CoreBinder.kt b/app/src/main/java/com/androiddevs/newsflash/di/modules/CoreBinder.kt index 3912ac9..6669ed6 100644 --- a/app/src/main/java/com/androiddevs/newsflash/di/modules/CoreBinder.kt +++ b/app/src/main/java/com/androiddevs/newsflash/di/modules/CoreBinder.kt @@ -2,11 +2,12 @@ package com.androiddevs.newsflash.di.modules import com.androiddevs.newsflash.data.local.shared_preferences.PreferencesHelper import com.androiddevs.newsflash.data.local.shared_preferences.PreferencesHelperImpl +import com.androiddevs.newsflash.data.network.NewsAPILayerImpl +import com.androiddevs.newsflash.data.network.contract.NewsAPILayer import com.androiddevs.newsflash.utils.AppDispatchers -import com.androiddevs.newsflash.utils.IDispatchers +import com.androiddevs.newsflash.utils.DispatcherProvider import dagger.Binds import dagger.Module -import dagger.Provides @Module @@ -16,5 +17,8 @@ abstract class CoreBinder { abstract fun bindsSharedPreferenceHelper(preferencesHelperImpl: PreferencesHelperImpl): PreferencesHelper @Binds - abstract fun bindsDispatchers(appDispatchers: AppDispatchers): IDispatchers + abstract fun bindsDispatchers(appDispatchers: AppDispatchers): DispatcherProvider + + @Binds + abstract fun bindsNewsRepo(newsAPILayerImpl: NewsAPILayerImpl): NewsAPILayer } \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/di/modules/CoreModule.kt b/app/src/main/java/com/androiddevs/newsflash/di/modules/CoreModule.kt index 632ed6f..f4b2d36 100644 --- a/app/src/main/java/com/androiddevs/newsflash/di/modules/CoreModule.kt +++ b/app/src/main/java/com/androiddevs/newsflash/di/modules/CoreModule.kt @@ -3,8 +3,6 @@ package com.androiddevs.newsflash.di.modules import android.app.Application import android.content.Context import android.content.SharedPreferences -import com.androiddevs.newsflash.utils.AppDispatchers -import com.androiddevs.newsflash.utils.IDispatchers import dagger.Module import dagger.Provides import javax.inject.Singleton diff --git a/app/src/main/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModel.kt b/app/src/main/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModel.kt index d50239f..f19e29b 100644 --- a/app/src/main/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModel.kt +++ b/app/src/main/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModel.kt @@ -3,15 +3,48 @@ package com.androiddevs.newsflash.ui.viewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.androiddevs.newsflash.data.network.apiwrapper.Status +import com.androiddevs.newsflash.data.network.models.NewsResult +import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest +import com.androiddevs.newsflash.data.repository.contract.NewsRepository +import com.androiddevs.newsflash.utils.DispatcherProvider +import kotlinx.coroutines.launch -class HomeFragmentViewModel : ViewModel() { +class HomeFragmentViewModel constructor( + private val newsRepository: NewsRepository, + private val appDispatchers: DispatcherProvider +) : ViewModel() { private val screenStates: MutableLiveData = MutableLiveData(HomeScreenStates.Loading) fun subscribeToUIState(): LiveData = screenStates + + init { + + } + + fun getTopHeadlines(headlinesRequest: TopHeadlinesRequest) { + viewModelScope.launch(appDispatchers.ioDispatcher) { + val response = newsRepository.getBusinessNews(headlinesRequest) + if (response.status == Status.SUCCESS) { + response.data?.let { + screenStates.postValue(HomeScreenStates.TopHeadlinesReceived(it.articles)) + } ?: kotlin.run { + screenStates.postValue(HomeScreenStates.ErrorState) + } + } else { + screenStates.postValue(HomeScreenStates.ErrorState) + } + } + } + } sealed class HomeScreenStates { object Loading : HomeScreenStates() + object ErrorState : HomeScreenStates() + data class TopHeadlinesReceived(val articleList: List) : + HomeScreenStates() } \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/utils/AppDispatchers.kt b/app/src/main/java/com/androiddevs/newsflash/utils/AppDispatchers.kt index 6f7e58f..85f4472 100644 --- a/app/src/main/java/com/androiddevs/newsflash/utils/AppDispatchers.kt +++ b/app/src/main/java/com/androiddevs/newsflash/utils/AppDispatchers.kt @@ -4,12 +4,13 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import javax.inject.Inject -class AppDispatchers @Inject constructor() : IDispatchers { +interface DispatcherProvider { + val mainDispatchers: CoroutineDispatcher + val ioDispatcher: CoroutineDispatcher +} + +class AppDispatchers @Inject constructor() : DispatcherProvider { override val mainDispatchers: CoroutineDispatcher = Dispatchers.Main override val ioDispatcher: CoroutineDispatcher = Dispatchers.IO -} -interface IDispatchers { - val mainDispatchers: CoroutineDispatcher - val ioDispatcher: CoroutineDispatcher } diff --git a/app/src/test/java/com/androiddevs/newsflash/data/repository/FakeNewsRepositoryImpl.kt b/app/src/test/java/com/androiddevs/newsflash/data/repository/FakeNewsRepositoryImpl.kt new file mode 100644 index 0000000..f2901bf --- /dev/null +++ b/app/src/test/java/com/androiddevs/newsflash/data/repository/FakeNewsRepositoryImpl.kt @@ -0,0 +1,34 @@ +package com.androiddevs.newsflash.data.repository + +import com.androiddevs.newsflash.data.network.apiwrapper.Response +import com.androiddevs.newsflash.data.network.apiwrapper.Status +import com.androiddevs.newsflash.data.network.models.NewsResult +import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest +import com.androiddevs.newsflash.data.repository.contract.NewsRepository +import javax.inject.Inject + +class FakeNewsRepositoryImpl @Inject constructor() : NewsRepository { + + var apiStatus = Status.SUCCESS + val successResponsePath = "news_top_headline_response/success_response.json" + + override suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Response { + return if (apiStatus == Status.SUCCESS) { + getSuccessResponse() + } else { + getErrorResponse() + } + } + + + fun getSuccessResponse(): Response { +// val response = +// loadModelFromResource(ClassLoader.getSystemClassLoader(), successResponsePath) + return Response.success(NewsResult.News()) + } + + private fun getErrorResponse(): Response { + return Response.error("", null) + } + +} \ No newline at end of file diff --git a/app/src/test/java/com/androiddevs/newsflash/di/components/FakeAppComponent.kt b/app/src/test/java/com/androiddevs/newsflash/di/components/FakeAppComponent.kt new file mode 100644 index 0000000..10f8028 --- /dev/null +++ b/app/src/test/java/com/androiddevs/newsflash/di/components/FakeAppComponent.kt @@ -0,0 +1,16 @@ +package com.androiddevs.newsflash.di.components + +import com.androiddevs.newsflash.di.modules.FakeAppBinders +import com.androiddevs.newsflash.ui.viewModel.HomeFragmentViewModelTest +import dagger.Component +import javax.inject.Singleton + +@Singleton +@Component(modules = [FakeAppBinders::class]) +interface FakeAppComponent { + fun inject(homeFragmentViewModelTest: HomeFragmentViewModelTest) + + companion object { + + } +} \ No newline at end of file diff --git a/app/src/test/java/com/androiddevs/newsflash/di/modules/FakeAppBinders.kt b/app/src/test/java/com/androiddevs/newsflash/di/modules/FakeAppBinders.kt new file mode 100644 index 0000000..f085cf1 --- /dev/null +++ b/app/src/test/java/com/androiddevs/newsflash/di/modules/FakeAppBinders.kt @@ -0,0 +1,24 @@ +package com.androiddevs.newsflash.di.modules + +import com.androiddevs.newsflash.data.repository.FakeNewsRepositoryImpl +import com.androiddevs.newsflash.utils.DispatcherProvider +import com.androiddevs.newsflash.utils.TestDispatcher +import dagger.Binds +import dagger.Module +import dagger.Provides + +@Module(includes = [FakeAppBinders.FakeAppModule::class]) +abstract class FakeAppBinders { + + @Binds + abstract fun bindTestDispatchers(testDispatcher: TestDispatcher): DispatcherProvider + + + @Module + internal object FakeAppModule { + + @Provides + fun provideFakeRepository(): FakeNewsRepositoryImpl = FakeNewsRepositoryImpl() + } + +} \ No newline at end of file diff --git a/app/src/test/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModelTest.kt b/app/src/test/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModelTest.kt index 19995d6..065032f 100644 --- a/app/src/test/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModelTest.kt +++ b/app/src/test/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModelTest.kt @@ -1,26 +1,73 @@ package com.androiddevs.newsflash.ui.viewModel -import org.junit.Assert.assertEquals +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.Observer +import com.androiddevs.newsflash.data.network.apiwrapper.Status +import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest +import com.androiddevs.newsflash.data.repository.FakeNewsRepositoryImpl +import com.androiddevs.newsflash.di.components.DaggerFakeAppComponent +import com.androiddevs.newsflash.utils.DispatcherProvider +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runBlockingTest import org.junit.Before +import org.junit.Rule import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import javax.inject.Inject - +@ExperimentalCoroutinesApi class HomeFragmentViewModelTest { - private val homeFragmentViewModel by lazy { HomeFragmentViewModel() } + @Rule + @JvmField + val rule = InstantTaskExecutorRule() + + @Inject + lateinit var fakeRepo: FakeNewsRepositoryImpl + + @Inject + lateinit var testDispatcher: DispatcherProvider + + private val homeFragmentViewModel: HomeFragmentViewModel by lazy { + HomeFragmentViewModel(fakeRepo, testDispatcher) + } + + @Mock + lateinit var observer: Observer + + private val headlinesRequest = TopHeadlinesRequest("in") @Before fun setUp() { - + MockitoAnnotations.initMocks(this) + DaggerFakeAppComponent.create().apply { + inject(this@HomeFragmentViewModelTest) + } } @Test fun `when page is created,default state should be loading`() { - assertEquals(HomeScreenStates.Loading, homeFragmentViewModel.subscribeToUIState().value) + homeFragmentViewModel.subscribeToUIState().observeForever(observer) + verify(observer).onChanged(HomeScreenStates.Loading) } @Test - fun `when page is created, then the top news headlines must be retrieved`() { - + fun `when headlines response is success, new state should be pushed`() = runBlockingTest { + homeFragmentViewModel.subscribeToUIState().observeForever(observer) + verify(observer).onChanged(HomeScreenStates.Loading) + homeFragmentViewModel.getTopHeadlines(headlinesRequest) + verify(observer).onChanged(HomeScreenStates.TopHeadlinesReceived(listOf())) } + + @Test + fun `when headlines response is unsuccessful, error state should be pushed`() = + runBlockingTest { + fakeRepo.apiStatus = Status.ERROR + homeFragmentViewModel.subscribeToUIState().observeForever(observer) + verify(observer).onChanged(HomeScreenStates.Loading) + homeFragmentViewModel.getTopHeadlines(headlinesRequest) + verify(observer).onChanged(HomeScreenStates.ErrorState) + } } \ No newline at end of file diff --git a/app/src/test/java/com/androiddevs/newsflash/utils/TestDispatcher.kt b/app/src/test/java/com/androiddevs/newsflash/utils/TestDispatcher.kt index 518e3fd..642085c 100644 --- a/app/src/test/java/com/androiddevs/newsflash/utils/TestDispatcher.kt +++ b/app/src/test/java/com/androiddevs/newsflash/utils/TestDispatcher.kt @@ -1,9 +1,13 @@ package com.androiddevs.newsflash.utils import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineDispatcher +import javax.inject.Inject -class TestDispatcher : IDispatchers { - override val mainDispatchers: CoroutineDispatcher = Dispatchers.Default - override val ioDispatcher: CoroutineDispatcher = Dispatchers.Default + +@ExperimentalCoroutinesApi +class TestDispatcher @Inject constructor() : DispatcherProvider { + override val mainDispatchers: CoroutineDispatcher = TestCoroutineDispatcher() + override val ioDispatcher: CoroutineDispatcher = TestCoroutineDispatcher() } \ No newline at end of file diff --git a/app/src/test/java/com/androiddevs/newsflash/utils/Utility.kt b/app/src/test/java/com/androiddevs/newsflash/utils/Utility.kt new file mode 100644 index 0000000..e1ffe1b --- /dev/null +++ b/app/src/test/java/com/androiddevs/newsflash/utils/Utility.kt @@ -0,0 +1,30 @@ +package com.androiddevs.newsflash.utils + +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import java.io.IOException +import java.nio.charset.Charset + + +inline fun loadModelFromResource(classLoader: ClassLoader, string: String): T{ + return Gson().fromJson(loadJSONFromAsset(classLoader, string)) +} + +inline fun Gson.fromJson(json: String) = this.fromJson(json, object: TypeToken() {}.type) + +fun loadJSONFromAsset(classLoader: ClassLoader,string: String): String { + + var json: String = "" + try { + val `is` = classLoader.getResourceAsStream(string) + val size = `is`.available() + val buffer = ByteArray(size) + `is`.read(buffer) + `is`.close() + json = String(buffer, Charset.forName("UTF-8")) + } catch (ex: IOException) { + ex.printStackTrace() + } + + return json +} \ No newline at end of file diff --git a/app/src/test/resources/news_top_headline_response/success_response.json b/app/src/test/resources/news_top_headline_response/success_response.json new file mode 100644 index 0000000..3014101 --- /dev/null +++ b/app/src/test/resources/news_top_headline_response/success_response.json @@ -0,0 +1,266 @@ +{ + "status": "ok", + "totalResults": 38, + "articles": [ + { + "source": { + "id": null, + "name": "New York Times" + }, + "author": "Katie Glueck, Thomas Kaplan", + "title": "Trump vs. Biden Live Updates: 2020 Election and Post Office - The New York Times", + "description": "Many questions remain as the first virtual convention nears. The Postal Service’s inspector general is investigating complaints about mail delays. And disinformation about Kamala Harris has surged this week.", + "url": "https://www.nytimes.com/live/2020/08/15/us/biden-vs-trump-election", + "urlToImage": "https://static01.nyt.com/images/2020/08/15/multimedia/15election-briefing-convention-sub-2/merlin_175577160_70bbdc9f-99aa-4151-877e-744f2d3e1438-facebookJumbo.jpg", + "publishedAt": "2020-08-15T17:25:01Z", + "content": "LiveUpdated Aug. 15, 2020, 2:55 p.m. ET\r\nAug. 15, 2020, 2:55 p.m. ET\r\nMany questions remain as the first virtual convention nears. The Postal Services inspector general is investigating complaints ab… [+16888 chars]" + }, + { + "source": { + "id": "cnn", + "name": "CNN" + }, + "author": "Artemis Moshtaghian, CNN", + "title": "Former Illinois Gov. James Thompson dead at 84 - CNN", + "description": "Former Illinois Gov. James R. Thompson died Friday night at the age of 84, CNN affiliate WLS reported.", + "url": "https://www.cnn.com/2020/08/15/politics/former-illinois-governor-james-thompson-dead/index.html", + "urlToImage": "https://cdn.cnn.com/cnnnext/dam/assets/200815120156-01-james-thompson il-governor-1988-super-tease.jpg", + "publishedAt": "2020-08-15T17:21:00Z", + "content": null + }, + { + "source": { + "id": null, + "name": "The Guardian" + }, + "author": "Lois Beckett", + "title": "Pelosi vows to protect USPS and accuses Trump of ‘openly working to destroy’ it - The Guardian", + "description": "House speaker said president is actively trying to ‘sabotage’ agency’s ability to deliver mail-in ballots in time for election", + "url": "https://www.theguardian.com/business/2020/aug/15/nancy-pelosi-trump-usps-election-mail-in-ballots", + "urlToImage": "https://i.guim.co.uk/img/media/10c0c0f868f6c8e0436600ef6862f6a1f5da0757/0_126_4500_2700/master/4500.jpg?width=1200&height=630&quality=85&auto=format&fit=crop&overlay-align=bottom%2Cleft&overlay-width=100p&overlay-base64=L2ltZy9zdGF0aWMvb3ZlcmxheXMvdGctZGVmYXVsdC5wbmc&enable=upscale&s=176596bed3da4ba49e47f4a336b8b7c4", + "publishedAt": "2020-08-15T16:48:00Z", + "content": "Top Democrat Nancy Pelosi has accused Donald Trump of openly working to destroy the post office, and said the US president is actively trying to sabotage the agencys ability to deliver Americans mail… [+4753 chars]" + }, + { + "source": { + "id": "nbc-news", + "name": "NBC News" + }, + "author": "Ariel Saramandi", + "title": "Mauritius residents join efforts to contain oil spill as grounded ship splits in two - NBC News", + "description": "The MV Wakashio ship which ran aground off Mauritius has broken into two, raising fears more oil will spill near the island. Residents are helping clean-up.", + "url": "https://www.nbcnews.com/news/world/mauritius-residents-join-efforts-contain-oil-spill-grounded-ship-splits-n1236868", + "urlToImage": "https://media2.s-nbcnews.com/j/newscms/2020_33/3404886/200815-mauritius-ship-break-jm-1129_1f2451984937d922adcc4495eda81ece.nbcnews-fp-1200-630.jpg", + "publishedAt": "2020-08-15T16:47:00Z", + "content": "FLOREAL, Mauritius As citizen-led rescue efforts continued, a ship that ran aground off the coast of Mauritius broke in two on Saturday, raising concerns that more oil would spill out near the coast … [+4815 chars]" + }, + { + "source": { + "id": null, + "name": "ESPN" + }, + "author": "Andrew Lopez, Adrian Wojnarowski", + "title": "New Orleans Pelicans dismiss head coach Alvin Gentry - ESPN", + "description": "The Pelicans have dismissed head coach Alvin Gentry following a disappointing run in the NBA's restart.", + "url": "https://www.espn.com/nba/story/_/id/29667535/sources-new-orleans-pelicans-dismiss-head-coach-alvin-gentry", + "urlToImage": "https://a.espncdn.com/combiner/i?img=%2Fphoto%2F2020%2F0815%2Fr731919_1296x729_16%2D9.jpg", + "publishedAt": "2020-08-15T16:29:18Z", + "content": "The New Orleans Pelicans dismissed coach Alvin Gentry, the team announced Saturday afternoon.\r\nAfter a disappointing performance in the NBA's Orlando restart, the franchise moved quickly on Gentry's … [+1776 chars]" + }, + { + "source": { + "id": "cnn", + "name": "CNN" + }, + "author": "Mary Ilyushina, Tara John and Vasco Cotovio, CNN", + "title": "Belarus leader calls Putin to reaffirm mutual cooperation, later rejects foreign mediation offers - CNN", + "description": "Belarusian President Alexander Lukashenko on Saturday rejected foreign mediation offers to help stabilize the volatile situation in his country. But reached out to Russian President Vladimir Putin with a call, and both men expressed confidence that the situat…", + "url": "https://www.cnn.com/2020/08/15/europe/belarus-protests-putin-call-intl/index.html", + "urlToImage": "https://cdn.cnn.com/cnnnext/dam/assets/200730091946-file-lukashenko-putin-super-tease.jpg", + "publishedAt": "2020-08-15T16:26:00Z", + "content": "Minsk, Belarus (CNN)Belarusian President Alexander Lukashenko on Saturday rejected foreign mediation offers to help stabilize the volatile situation in his country. But reached out to Russian Preside… [+4203 chars]" + }, + { + "source": { + "id": "fox-news", + "name": "Fox News" + }, + "author": "Paulina Dedaj", + "title": "Bruins' Tuukka Rask opts out of NHL season, hours before Game 3 after calling playoffs 'dull' - Fox News", + "description": "Boston Bruins veteran goalie Tuukka Rask announced just hours before Saturday’s Game 3 against the Carolina Hurricanes that he would be opting out of the NHL’s return to Play to be with his family.", + "url": "https://www.foxnews.com/sports/bruins-tuukka-rask-opts-out-nhl-game-3-playoffs-dull", + "urlToImage": "https://static.foxnews.com/foxnews.com/content/uploads/2019/04/NHL-Tuuka-Rask.jpg", + "publishedAt": "2020-08-15T16:03:40Z", + "content": "Boston Bruins veteran goalie Tuukka Rask announced just hours before Saturday’s Game 3 against the Carolina Hurricanes that he would be opting out of the NHL’s return to Play to be with his family.\r\n… [+1915 chars]" + }, + { + "source": { + "id": "fox-news", + "name": "Fox News" + }, + "author": "Adam Shaw", + "title": "Iran fumes, warns of 'dangerous future' for UAE over historic US-brokered deal with Israel - Fox News", + "description": "Iran’s Revolutionary Guard warned of a “dangerous future” for the United Arab Emirates over a U.S.-brokered agreement that sees the UAE open up diplomatic relations with Israel.", + "url": "https://www.foxnews.com/politics/iran-fumes-warns-of-dangerous-future-for-uae-over-historic-us-brokered-deal-with-israel", + "urlToImage": "https://cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/d46a8c86-2b64-463e-a52a-799c3213c543/ffc7cdce-2599-4f8c-adba-d98061cd0061/1280x720/match/image.jpg", + "publishedAt": "2020-08-15T15:51:13Z", + "content": "Iran’s Revolutionary Guard warned of a “dangerous future” for the United Arab Emirates over a U.S.-brokered agreement that sees the UAE open up diplomatic relations with Israel.\r\nThe Iranian guard ca… [+2871 chars]" + }, + { + "source": { + "id": null, + "name": "Billboard" + }, + "author": "Mitchell Peters", + "title": "Chrissy Teigen Opens Up About Being Pregnant During Breast Surgery: 'It's Quite a Story' - Billboard", + "description": "", + "url": "https://www.billboard.com/articles/news/9435031/chrissy-teigen-john-legend-pregnant-breast-surgery", + "urlToImage": "https://static.billboard.com/files/2020/03/john-legend-chrissy-teigen-feb-2020-b-billboard-1548-1584654205-1024x677.jpg", + "publishedAt": "2020-08-15T15:34:24Z", + "content": "Chrissy Teigen has \"quite a story\" to tell about her surprise pregnancy.\r\nOne day after revealing she and husband John Legend are expecting their third child, the 34-year-old model and author opened … [+2881 chars]" + }, + { + "source": { + "id": null, + "name": "PEOPLE" + }, + "author": "Maria Pasquini", + "title": "Nikki Bella Is Enjoying Spending Time with Newborn Son: 'I Have Never Cried So Many Happy Tears' - Yahoo Entertainment", + "description": "\"I have taken in every single second with our baby boy. And will continue to do so,\" Nikki Bella said", + "url": "https://people.com/parents/nikki-bella-spending-time-son-cried-happy-tears/", + "urlToImage": "https://imagesvc.meredithcorp.io/v3/mm/image?q=85&c=sc&poi=%5B513%2C386%5D&w=950&h=497&url=https%3A%2F%2Fstatic.onecms.io%2Fwp-content%2Fuploads%2Fsites%2F20%2F2020%2F08%2F15%2Fnikkie-bella.jpg", + "publishedAt": "2020-08-15T15:30:00Z", + "content": "Nikki Bella is enjoying taking time off to spend with her newborn son.\r\nAs the mom of one faced off in the EA Sports UFC Virtual Fight Card showdown against twin sister Brie Bella — who welcomed her … [+2508 chars]" + }, + { + "source": { + "id": null, + "name": "MMA Fighting" + }, + "author": "Mike Heck", + "title": "Colby Covington leaning toward ‘Natty GOAT’ Daniel Cormier to defeat ‘great’ Stipe Miocic at UFC 252 - MMA Fighting", + "description": "Despite his typically brash persona, Colby Covington has a lot of respect for both competitors of Saturday night’s championship fight in Las Vegas.", + "url": "https://www.mmafighting.com/2020/8/15/21364816/colby-covington-leaning-toward-natty-goat-daniel-cormier-to-defeat-great-stipe-miocic-at-ufc-252", + "urlToImage": "https://cdn.vox-cdn.com/thumbor/Z6X6WYrnik9rwhUAf6ntMUIqbos=/0x0:2880x1508/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/19525945/138_Colby_Covington.jpg", + "publishedAt": "2020-08-15T15:00:00Z", + "content": "Despite his typically brash persona, Colby Covington has a lot of respect for both competitors of Saturday nights championship fight in Las Vegas.\r\nIn the main event of UFC 252, Stipe Miocic will def… [+3115 chars]" + }, + { + "source": { + "id": null, + "name": "Yahoo Entertainment" + }, + "author": "Raechal Shewfelt", + "title": "Meghan Markle says returning to the U.S. after a decade away was 'just devastating' - Yahoo Entertainment", + "description": "The Duchess of Sussex had been in Canada filming before relocating to London with Prince Harry.", + "url": "https://www.yahoo.com/entertainment/meghan-markle-returning-united-states-devastating-145719193.html", + "urlToImage": "https://s.yimg.com/uu/api/res/1.2/2Pbfjshq0eszCkcVTfkkQA--~B/aD03MTQ7dz0xMDI0O3NtPTE7YXBwaWQ9eXRhY2h5b24-/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-uploaded-images/2020-08/175006a0-de72-11ea-9f93-48e27ec15c53", + "publishedAt": "2020-08-15T14:57:00Z", + "content": "As Meghan Markle returned to the United States in the spring, royal husband Prince Harry in tow, the country had shut down due to the coronavirus pandemic and protestors filled the streets to call ou… [+3185 chars]" + }, + { + "source": { + "id": "the-verge", + "name": "The Verge" + }, + "author": "Kim Lyons", + "title": "Mozilla and Google reportedly renew Firefox search agreement - The Verge", + "description": "Mozilla and Google have extended their arrangement to keep Google the default search engine within the Firefox browser until at least 2023, and are expected to formally announce the deal later this fall.", + "url": "https://www.theverge.com/2020/8/15/21370020/mozilla-google-firefox-search-engine-browser", + "urlToImage": "https://cdn.vox-cdn.com/thumbor/78RxNPEIrphXEabfHripYCBLLUA=/0x146:2040x1214/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/19704536/acastro_200207_3900_firefox_0001.0.jpg", + "publishedAt": "2020-08-15T14:52:59Z", + "content": "The deal will keep Google as the default search engine in the Firefox browser\r\nIllustration by Alex Castro / The Verge\r\nMozilla and Google have extended their arrangement to keep Google the default s… [+1257 chars]" + }, + { + "source": { + "id": "cbs-news", + "name": "CBS News" + }, + "author": "CBS News", + "title": "Evacuations remain in place as Lake Fire in California continues to rage - CBS News", + "description": "Some parts of the Angeles National Forest have not burned since the 1960s, leaving decades of brush ready to ignite.", + "url": "https://www.cbsnews.com/news/lake-fire-california-evacuations-lake-hughes/", + "urlToImage": "https://cbsnews1.cbsistatic.com/hub/i/r/2020/08/15/28ca0925-74e6-41e9-ad83-1d9bf2cf064d/thumbnail/1200x630/6013d1754baf0197b95a1f75bd0edded/ap-20226838383764.jpg", + "publishedAt": "2020-08-15T14:51:00Z", + "content": "Firefighters are continuing to contend with a wildfire in California that has destroyed homes and forced people to flee. The Lake Fire, burning near Lake Hughes north of Los Angeles, was 12% containe… [+2251 chars]" + }, + { + "source": { + "id": null, + "name": "New York Post" + }, + "author": "Mollie Walker", + "title": "Reds game postponed as player tests positive for coronavirus - New York Post ", + "description": "MLB was forced to postpone games Saturday and Sunday between the Reds and Pirates in Cincinnati after an unidentified Reds player tested positive for the coronavirus a day before, The Post’s …", + "url": "https://nypost.com/2020/08/15/reds-pirates-game-postponed-as-player-gets-coronavirus/", + "urlToImage": "https://nypost.com/wp-content/uploads/sites/2/2020/08/Reds-player-tests-positive-for-the-coronavirus..jpg?quality=90&strip=all&w=1200", + "publishedAt": "2020-08-15T14:47:00Z", + "content": "MLB was forced to postpone games Saturday and Sunday between the Reds and Pirates in Cincinnati after an unidentified Reds player tested positive for the coronavirus a day before, The Post’s Joel She… [+1246 chars]" + }, + { + "source": { + "id": null, + "name": "Yahoo Entertainment" + }, + "author": "Leila Miller, Luke Money, Paul Duginski, Tony Barboza, Jack Dolan", + "title": "Severe heat wave causes rolling blackouts, dangerous conditions across California - Yahoo News", + "description": "The broiling conditions that began Friday in California may rival the deadly seven-day heat event of July 2006.", + "url": "https://news.yahoo.com/worst-heat-wave-years-causes-144343229.html", + "urlToImage": "https://s.yimg.com/uu/api/res/1.2/vd3OmL8BmP_AG.3zrW_ehg--~B/aD01NjA7dz04NDA7c209MTthcHBpZD15dGFjaHlvbg--/https://media.zenfs.com/en/la_times_articles_853/8eeab3689f1cb8a6079834fd3e715ecb", + "publishedAt": "2020-08-15T14:43:00Z", + "content": "A cool breeze propels windsurfers across the shimmering waters off Cabrillo Beach in San Pedro on Friday, Aug. 14, 2020. Southern California is in the grips of a heat wave, with temperatures in the 8… [+5669 chars]" + }, + { + "source": { + "id": null, + "name": "Forbes" + }, + "author": "Bruce Y. Lee", + "title": "How Long Are You Immune After Covid-19 Coronavirus? Here Is What CDC Now Suggests - Forbes", + "description": "The Centers for Disease Control and Prevention (CDC) has some new guidance about whether you should get tested for SARS-CoV2 or quarantine after recovering from Covid-19.", + "url": "https://www.forbes.com/sites/brucelee/2020/08/15/how-long-are-you-immune-after-covid-19-coronavirus-here-is-what-cdc-now-suggests/", + "urlToImage": "https://thumbor.forbes.com/thumbor/fit-in/1200x0/filters%3Aformat%28jpg%29/https%3A%2F%2Fspecials-images.forbesimg.com%2Fimageserve%2F5f37eb0248f19f60b1ccc51c%2F0x0.jpg", + "publishedAt": "2020-08-15T14:34:00Z", + "content": "Should you get re-tested for the Covid-19 coronavirus after you have recovered from Covid-19? Well, ... [+] the CDC has some new guidance, sort of. (Photo by Robin Utrecht/SOPA Images/LightRocket via… [+9650 chars]" + }, + { + "source": { + "id": null, + "name": "MarketWatch" + }, + "author": "Mark DeCambre", + "title": "Stock market looks like 'Wile E. Coyote,' running off 'a cliff,' says expert - MarketWatch", + "description": "‘ Never before have I seen a market so highly valued in the face of overwhelming uncertainty,’ says GMO’s Montier", + "url": "https://www.marketwatch.com/story/stock-market-looks-like-hapless-wile-e-coyote-running-off-the-edge-of-a-cliff-says-behavioral-economist-11597501544", + "urlToImage": "https://images.mktw.net/im-221308?size=1.5980024968789013", + "publishedAt": "2020-08-15T14:25:00Z", + "content": "The S&P 500 index is teetering on the edge of a rarefied perch, persistently brushing aside uncertainties created by the COVID-19 pandemic in its ascent. Although arguably the most important stoc… [+6411 chars]" + }, + { + "source": { + "id": "fox-news", + "name": "Fox News" + }, + "author": "Melissa Roberto", + "title": "Kanye West takes subtle jab at Taylor Swift in latest Twitter thread - Fox News", + "description": "Kanye West shaded Taylor Swift on Friday on Twitter.", + "url": "https://www.foxnews.com/entertainment/kanye-west-subtle-jab-taylor-swift-twitter", + "urlToImage": "https://static.foxnews.com/foxnews.com/content/uploads/2019/08/vmas-taylor-swift-kanye-west-2019.jpg", + "publishedAt": "2020-08-15T14:17:40Z", + "content": "Kanye West seemingly shaded Taylor Swift on Friday by bringing up quite a touchy subject in their years-long feud.\r\nThe 43-year-old rapper and 2020 presidential hopeful announced on Friday he was goi… [+2584 chars]" + }, + { + "source": { + "id": "fox-news", + "name": "Fox News" + }, + "author": "Victor Garcia", + "title": "Eric Trump calls on Biden to answer for 'very discriminatory' process of picking running mate - Fox News", + "description": "The Biden campaign's process that resulted in the selection of Sen. Kamala Harris, D-Calif., as their vice presidential pick is \"discriminatory,\" Trump organization vice president Eric Trump said on the latest \"Fox News Rundown podcast,\" calling the pick \"ins…", + "url": "https://www.foxnews.com/politics/eric-trump-biden-running-mate-discriminatory-process", + "urlToImage": "https://cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/c236ecab-258d-47c2-82ce-2d266417b65d/650bac8a-57b3-4a7e-8a6d-bf3f5d39032a/1280x720/match/image.jpg", + "publishedAt": "2020-08-15T14:06:34Z", + "content": "The Biden campaign's process that resulted in the selection of Sen. Kamala Harris, D-Calif., as the presumptive Democratic nominee's running mate was \"very discriminatory,\" Trump Organization vice pr… [+2528 chars]" + } + ] +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 945135a..b434b4b 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.5.4' + classpath 'com.android.tools.build:gradle:4.1.0-rc01' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d4ba5af..e921ae9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Nov 01 20:41:27 CET 2019 +#Sun Aug 16 01:11:04 IST 2020 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.5-all.zip From 14ffd47562617fffb563cd40731ceae46d3cbc32 Mon Sep 17 00:00:00 2001 From: piyushpradeepkumar Date: Tue, 18 Aug 2020 18:37:30 +0530 Subject: [PATCH 07/15] added repository tests --- .../data/network/NewsAPILayerImpl.kt | 31 ++++++++++++ .../data/repository/FakeNewsRepositoryImpl.kt | 9 ++-- .../data/repository/NewsRepositoryImplTest.kt | 47 +++++++++++++++++++ .../di/components/FakeAppComponent.kt | 2 + .../newsflash/di/modules/FakeAppBinders.kt | 4 ++ .../newsflash/utils/TestableAPIStatus.kt | 7 +++ 6 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 app/src/test/java/com/androiddevs/newsflash/data/network/NewsAPILayerImpl.kt create mode 100644 app/src/test/java/com/androiddevs/newsflash/data/repository/NewsRepositoryImplTest.kt create mode 100644 app/src/test/java/com/androiddevs/newsflash/utils/TestableAPIStatus.kt diff --git a/app/src/test/java/com/androiddevs/newsflash/data/network/NewsAPILayerImpl.kt b/app/src/test/java/com/androiddevs/newsflash/data/network/NewsAPILayerImpl.kt new file mode 100644 index 0000000..b098805 --- /dev/null +++ b/app/src/test/java/com/androiddevs/newsflash/data/network/NewsAPILayerImpl.kt @@ -0,0 +1,31 @@ +package com.androiddevs.newsflash.data.network + +import com.androiddevs.newsflash.data.network.apiwrapper.Response +import com.androiddevs.newsflash.data.network.apiwrapper.Status +import com.androiddevs.newsflash.data.network.apiwrapper.handleResponse +import com.androiddevs.newsflash.data.network.contract.NewsAPILayer +import com.androiddevs.newsflash.data.network.models.NewsResult +import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest +import com.androiddevs.newsflash.utils.TestableAPIStatus +import com.androiddevs.newsflash.utils.loadModelFromResource + +class NewsAPILayerImpl : NewsAPILayer, TestableAPIStatus { + + override var apiStatus = Status.SUCCESS + private val successResponsePath = "news_top_headline_response/success_response.json" + + + override suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Response { + return if (apiStatus == Status.SUCCESS) { + val response = + loadModelFromResource( + ClassLoader.getSystemClassLoader(), + successResponsePath + ) + Response.success(response) + } else { + Response.error("Something went wrong. Please try again later", null) + } + + } +} \ No newline at end of file diff --git a/app/src/test/java/com/androiddevs/newsflash/data/repository/FakeNewsRepositoryImpl.kt b/app/src/test/java/com/androiddevs/newsflash/data/repository/FakeNewsRepositoryImpl.kt index f2901bf..d45b2b8 100644 --- a/app/src/test/java/com/androiddevs/newsflash/data/repository/FakeNewsRepositoryImpl.kt +++ b/app/src/test/java/com/androiddevs/newsflash/data/repository/FakeNewsRepositoryImpl.kt @@ -5,12 +5,13 @@ import com.androiddevs.newsflash.data.network.apiwrapper.Status import com.androiddevs.newsflash.data.network.models.NewsResult import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest import com.androiddevs.newsflash.data.repository.contract.NewsRepository +import com.androiddevs.newsflash.utils.TestableAPIStatus import javax.inject.Inject -class FakeNewsRepositoryImpl @Inject constructor() : NewsRepository { +class FakeNewsRepositoryImpl @Inject constructor() : NewsRepository, TestableAPIStatus { + + override var apiStatus = Status.SUCCESS - var apiStatus = Status.SUCCESS - val successResponsePath = "news_top_headline_response/success_response.json" override suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Response { return if (apiStatus == Status.SUCCESS) { @@ -22,8 +23,6 @@ class FakeNewsRepositoryImpl @Inject constructor() : NewsRepository { fun getSuccessResponse(): Response { -// val response = -// loadModelFromResource(ClassLoader.getSystemClassLoader(), successResponsePath) return Response.success(NewsResult.News()) } diff --git a/app/src/test/java/com/androiddevs/newsflash/data/repository/NewsRepositoryImplTest.kt b/app/src/test/java/com/androiddevs/newsflash/data/repository/NewsRepositoryImplTest.kt new file mode 100644 index 0000000..6a4ab72 --- /dev/null +++ b/app/src/test/java/com/androiddevs/newsflash/data/repository/NewsRepositoryImplTest.kt @@ -0,0 +1,47 @@ +package com.androiddevs.newsflash.data.repository + +import com.androiddevs.newsflash.data.network.NewsAPILayerImpl +import com.androiddevs.newsflash.data.network.apiwrapper.Status +import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest +import com.androiddevs.newsflash.di.components.DaggerFakeAppComponent +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Before +import org.junit.Test +import org.mockito.MockitoAnnotations +import javax.inject.Inject + +@ExperimentalCoroutinesApi +class NewsRepositoryImplTest { + + @Inject + lateinit var fakeAPILayer: NewsAPILayerImpl + + private val repository by lazy { NewsRepositoryImpl(fakeAPILayer) } + + private val headlinesRequest = TopHeadlinesRequest("in") + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + DaggerFakeAppComponent.create().apply { + inject(this@NewsRepositoryImplTest) + } + } + + @Test + fun `when a successful request is made, repository should return proper values`() = + runBlockingTest { + val response = repository.getBusinessNews(headlinesRequest) + assert(response.data != null) + } + + @Test + fun `when the api is unsuccessful, repository should return proper error message`() = + runBlockingTest { + fakeAPILayer.apiStatus = Status.ERROR + val response = repository.getBusinessNews(headlinesRequest) + assert(response.message!!.isNotEmpty()) + } +} + diff --git a/app/src/test/java/com/androiddevs/newsflash/di/components/FakeAppComponent.kt b/app/src/test/java/com/androiddevs/newsflash/di/components/FakeAppComponent.kt index 10f8028..f65f942 100644 --- a/app/src/test/java/com/androiddevs/newsflash/di/components/FakeAppComponent.kt +++ b/app/src/test/java/com/androiddevs/newsflash/di/components/FakeAppComponent.kt @@ -1,5 +1,6 @@ package com.androiddevs.newsflash.di.components +import com.androiddevs.newsflash.data.repository.NewsRepositoryImplTest import com.androiddevs.newsflash.di.modules.FakeAppBinders import com.androiddevs.newsflash.ui.viewModel.HomeFragmentViewModelTest import dagger.Component @@ -9,6 +10,7 @@ import javax.inject.Singleton @Component(modules = [FakeAppBinders::class]) interface FakeAppComponent { fun inject(homeFragmentViewModelTest: HomeFragmentViewModelTest) + fun inject(homeFragmentViewModelTest: NewsRepositoryImplTest) companion object { diff --git a/app/src/test/java/com/androiddevs/newsflash/di/modules/FakeAppBinders.kt b/app/src/test/java/com/androiddevs/newsflash/di/modules/FakeAppBinders.kt index f085cf1..aa6cb27 100644 --- a/app/src/test/java/com/androiddevs/newsflash/di/modules/FakeAppBinders.kt +++ b/app/src/test/java/com/androiddevs/newsflash/di/modules/FakeAppBinders.kt @@ -1,5 +1,6 @@ package com.androiddevs.newsflash.di.modules +import com.androiddevs.newsflash.data.network.NewsAPILayerImpl import com.androiddevs.newsflash.data.repository.FakeNewsRepositoryImpl import com.androiddevs.newsflash.utils.DispatcherProvider import com.androiddevs.newsflash.utils.TestDispatcher @@ -19,6 +20,9 @@ abstract class FakeAppBinders { @Provides fun provideFakeRepository(): FakeNewsRepositoryImpl = FakeNewsRepositoryImpl() + + @Provides + fun provideFakeApiLayer(): NewsAPILayerImpl = NewsAPILayerImpl() } } \ No newline at end of file diff --git a/app/src/test/java/com/androiddevs/newsflash/utils/TestableAPIStatus.kt b/app/src/test/java/com/androiddevs/newsflash/utils/TestableAPIStatus.kt new file mode 100644 index 0000000..f11c095 --- /dev/null +++ b/app/src/test/java/com/androiddevs/newsflash/utils/TestableAPIStatus.kt @@ -0,0 +1,7 @@ +package com.androiddevs.newsflash.utils + +import com.androiddevs.newsflash.data.network.apiwrapper.Status + +interface TestableAPIStatus { + var apiStatus: Status +} \ No newline at end of file From d1bce7e377ac2ba0eb3328dc0dce6a51afc359c8 Mon Sep 17 00:00:00 2001 From: piyushpradeepkumar Date: Sat, 22 Aug 2020 16:55:11 +0530 Subject: [PATCH 08/15] renamed classes and added repository tests --- .idea/codeStyles/Project.xml | 16 ++++++++ app/build.gradle | 12 +++--- .../data/network/NewsAPILayerImpl.kt | 6 +-- .../newsflash/data/network/NewsApiService.kt | 10 ++--- .../network/apiwrapper/RetrofitAPIWrapper.kt | 22 +++++----- .../data/network/contract/NewsAPILayer.kt | 6 +-- .../data/network/models/ApiResponse.kt | 40 ------------------- .../newsflash/data/network/models/News.kt | 23 +++++++++++ .../data/network/models/NewsError.kt | 12 +++--- .../data/network/models/NewsResult.kt | 26 ------------ .../data/network/models/NewsSources.kt | 21 ---------- .../newsflash/data/network/models/Sources.kt | 16 ++++++++ .../data/repository/NewsRepositoryImpl.kt | 20 ++++++++-- .../repository/contract/NewsRepository.kt | 6 +-- .../repository/mapper/NewsArticleMapper.kt | 16 ++++++++ .../data/repository/models/NewsArticle.kt | 13 ++++++ .../ui/adapter/HomeNewsRecyelerAdapter.kt | 9 +++-- .../ui/viewModel/HomeFragmentViewModel.kt | 6 +-- app/src/main/res/layout/home_news_row.xml | 2 +- .../data/repository/NewsRepositoryImplTest.kt | 18 +++++---- .../fakes/FakeNewsAPILayerImpl.kt} | 17 ++++---- .../newsflash/di/modules/FakeAppBinders.kt | 6 +-- .../ui/viewModel/HomeFragmentViewModelTest.kt | 2 +- .../fakes}/FakeNewsRepositoryImpl.kt | 16 ++++---- build.gradle | 2 +- 25 files changed, 178 insertions(+), 165 deletions(-) delete mode 100644 app/src/main/java/com/androiddevs/newsflash/data/network/models/ApiResponse.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/data/network/models/News.kt delete mode 100644 app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsResult.kt delete mode 100644 app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsSources.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/data/network/models/Sources.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/data/repository/mapper/NewsArticleMapper.kt create mode 100644 app/src/main/java/com/androiddevs/newsflash/data/repository/models/NewsArticle.kt rename app/src/test/java/com/androiddevs/newsflash/data/{network/NewsAPILayerImpl.kt => repository/fakes/FakeNewsAPILayerImpl.kt} (60%) rename app/src/test/java/com/androiddevs/newsflash/{data/repository => ui/viewModel/fakes}/FakeNewsRepositoryImpl.kt (59%) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 88ea3aa..3cc336b 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,6 +1,22 @@ + + diff --git a/app/build.gradle b/app/build.gradle index 500ef90..400f58f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -47,14 +47,14 @@ android { dependencies { def retrofitVersion = "2.9.0" - def okHttpVersion = "4.7.2" + def okHttpVersion = '4.8.1' def navVersion = "2.3.0" def lifecycle_version = "2.2.0" implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.core:core-ktx:1.3.1' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.cardview:cardview:1.0.0' @@ -79,10 +79,10 @@ dependencies { testImplementation "androidx.arch.core:core-testing:2.1.0" // DAGGER - implementation 'com.google.dagger:dagger:2.28' - kapt 'com.google.dagger:dagger-compiler:2.28' - kaptTest 'com.google.dagger:dagger-compiler:2.28' - annotationProcessor 'com.google.dagger:dagger-compiler:2.28' + implementation 'com.google.dagger:dagger:2.28.3' + kapt 'com.google.dagger:dagger-compiler:2.28.3' + kaptTest 'com.google.dagger:dagger-compiler:2.28.3' + annotationProcessor 'com.google.dagger:dagger-compiler:2.28.3' //VIEW-MODEL AND LIVE DATA diff --git a/app/src/main/java/com/androiddevs/newsflash/data/network/NewsAPILayerImpl.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/NewsAPILayerImpl.kt index f357d85..f336972 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/network/NewsAPILayerImpl.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/NewsAPILayerImpl.kt @@ -1,10 +1,10 @@ package com.androiddevs.newsflash.data.network import com.androiddevs.newsflash.constants.API_KEY -import com.androiddevs.newsflash.data.network.apiwrapper.Response +import com.androiddevs.newsflash.data.network.apiwrapper.Resource import com.androiddevs.newsflash.data.network.apiwrapper.handleResponse import com.androiddevs.newsflash.data.network.contract.NewsAPILayer -import com.androiddevs.newsflash.data.network.models.NewsResult +import com.androiddevs.newsflash.data.network.models.News import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest import retrofit2.Retrofit import javax.inject.Inject @@ -16,7 +16,7 @@ class NewsAPILayerImpl @Inject constructor(retrofit: Retrofit) : retrofit.create(NewsApiService::class.java) } - override suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Response { + override suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Resource { return handleResponse { newsApiService.getTopHeadlines( topHeadlinesRequest.country, diff --git a/app/src/main/java/com/androiddevs/newsflash/data/network/NewsApiService.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/NewsApiService.kt index 0c50f4d..68a1351 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/network/NewsApiService.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/NewsApiService.kt @@ -1,7 +1,7 @@ package com.androiddevs.newsflash.data.network -import com.androiddevs.newsflash.data.network.models.NewsResult -import com.androiddevs.newsflash.data.network.models.NewsSources +import com.androiddevs.newsflash.data.network.models.News +import com.androiddevs.newsflash.data.network.models.Sources import retrofit2.http.GET import retrofit2.http.Query @@ -10,7 +10,7 @@ interface NewsApiService { fun getNewsSources( @Query("category") category: String, @Query("language") language: String, @Query("country") country: String - ): NewsSources.Sources + ): Sources @GET("top-headlines") fun getTopHeadlines( @@ -21,7 +21,7 @@ interface NewsApiService { @Query("pageSize") pageSize: Int, @Query("page") page: Int, @Query("apiKey") apiKey: String? - ): NewsResult.News + ): News @GET("everything") fun getEverything( @@ -36,6 +36,6 @@ interface NewsApiService { @Query("sortBy") sortBy: String, @Query("pageSize") pageSize: Int, @Query("page") page: Int - ): NewsResult.News + ): News } \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/network/apiwrapper/RetrofitAPIWrapper.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/apiwrapper/RetrofitAPIWrapper.kt index e549b42..2c2227a 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/network/apiwrapper/RetrofitAPIWrapper.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/apiwrapper/RetrofitAPIWrapper.kt @@ -1,19 +1,19 @@ package com.androiddevs.newsflash.data.network.apiwrapper -suspend fun handleResponse(apiBlock: suspend () -> T): Response { +suspend fun handleResponse(apiBlock: suspend () -> T): Resource { return try { val response = apiBlock() if (response != null) { - Response.success(response) + Resource.success(response) } else { - Response.error("Something went wrong. Please try again later") + Resource.getDefaultError() } } catch (exception: java.lang.Exception) { - Response.error("Something went wrong. Please try again later", exception) + Resource.error("Something went wrong. Please try again later", exception) } } -data class Response( +data class Resource( val status: Status, val data: T? = null, val message: String? = null, @@ -21,12 +21,16 @@ data class Response( ) { companion object { - fun success(data: T?): Response { - return Response(Status.SUCCESS, data) + fun success(data: T?): Resource { + return Resource(Status.SUCCESS, data) } - fun error(errorMessage: String? = null, exception: Exception? = null): Response { - return Response(Status.ERROR, message = errorMessage, errorException = exception) + fun error(errorMessage: String? = null, exception: Exception? = null): Resource { + return Resource(Status.ERROR, message = errorMessage, errorException = exception) + } + + fun getDefaultError(): Resource { + return error("Something went wrong. Please try again later") } } } diff --git a/app/src/main/java/com/androiddevs/newsflash/data/network/contract/NewsAPILayer.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/contract/NewsAPILayer.kt index 88cd290..b77931e 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/network/contract/NewsAPILayer.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/contract/NewsAPILayer.kt @@ -1,9 +1,9 @@ package com.androiddevs.newsflash.data.network.contract -import com.androiddevs.newsflash.data.network.apiwrapper.Response -import com.androiddevs.newsflash.data.network.models.NewsResult +import com.androiddevs.newsflash.data.network.apiwrapper.Resource +import com.androiddevs.newsflash.data.network.models.News import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest interface NewsAPILayer { - suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Response + suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Resource } \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/network/models/ApiResponse.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/models/ApiResponse.kt deleted file mode 100644 index 93a6c79..0000000 --- a/app/src/main/java/com/androiddevs/newsflash/data/network/models/ApiResponse.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.androiddevs.newsflash.data.network.models - -import retrofit2.Call -import retrofit2.Response - -class ApiResponse { - - var code: Int = 0 - var body: T? - var errorMessage: String? = null - var shouldShowRetryButton: Boolean = false - private var retrofitCall: Call - - constructor(error: Throwable, call: Call) { - code = 500 - body = null - errorMessage = error.message - shouldShowRetryButton = true - this.retrofitCall = call - } - - constructor(response: Response, call: Call) { - this.retrofitCall = call - code = response.code() - if (response.isSuccessful) { - body = response.body() - } else { - shouldShowRetryButton = true - var message: String? = null - response.errorBody()?.let { - message = it.string() - } - if (message.isNullOrEmpty()) { - message = response.message() - } - errorMessage = message - body = null - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/network/models/News.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/models/News.kt new file mode 100644 index 0000000..68a9608 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/models/News.kt @@ -0,0 +1,23 @@ +package com.androiddevs.newsflash.data.network.models + +data class News( + val status: String? = null, + val totalResults: Int = 0, + val articles: List
= listOf() +) { + data class Article( + val source: Source? = null, + val author: String, + val title: String, + val description: String, + val url: String, + val urlToImage: String, + val publishedAt: String? = null, + val content: String? = null + ) { + data class Source( + val id: String? = null, + val name: String? = null + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsError.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsError.kt index 44b9249..7866d35 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsError.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsError.kt @@ -1,9 +1,7 @@ package com.androiddevs.newsflash.data.network.models -object NewsError { - data class Error( - val status: String? = null, - val code: String? = null, - val message: String? = null - ) -} \ No newline at end of file +data class Error( + val status: String? = null, + val code: String? = null, + val message: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsResult.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsResult.kt deleted file mode 100644 index 33d9ffe..0000000 --- a/app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsResult.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.androiddevs.newsflash.data.network.models - -object NewsResult { - - data class News( - val status: String? = null, - val totalResults: Int = 0, - val articles: List
= listOf() - ) { - data class Article( - val source: Source? = null, - val author: String? = null, - val title: String? = null, - val description: String? = null, - val url: String? = null, - val urlToImage: String? = null, - val publishedAt: String? = null, - val content: String? = null - ) { - data class Source( - val id: String? = null, - val name: String? = null - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsSources.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsSources.kt deleted file mode 100644 index 3ab4e49..0000000 --- a/app/src/main/java/com/androiddevs/newsflash/data/network/models/NewsSources.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.androiddevs.newsflash.data.network.models - -object NewsSources -{ - - data class Sources( - val status: String? = null, - val sources: List = listOf() - ) - { - data class Source( - val id: String? = null, - val name: String? = null, - val description: String? = null, - val url: String? = null, - val category: String? = null, - val language: String? = null, - val country: String? = null - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/network/models/Sources.kt b/app/src/main/java/com/androiddevs/newsflash/data/network/models/Sources.kt new file mode 100644 index 0000000..d9979ee --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/data/network/models/Sources.kt @@ -0,0 +1,16 @@ +package com.androiddevs.newsflash.data.network.models + +data class Sources( + val status: String? = null, + val sources: List = listOf() +) { + data class Source( + val id: String? = null, + val name: String? = null, + val description: String? = null, + val url: String? = null, + val category: String? = null, + val language: String? = null, + val country: String? = null + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/repository/NewsRepositoryImpl.kt b/app/src/main/java/com/androiddevs/newsflash/data/repository/NewsRepositoryImpl.kt index db0d22f..dceffe8 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/repository/NewsRepositoryImpl.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/repository/NewsRepositoryImpl.kt @@ -1,15 +1,27 @@ package com.androiddevs.newsflash.data.repository -import com.androiddevs.newsflash.data.network.apiwrapper.Response +import com.androiddevs.newsflash.data.network.apiwrapper.Resource +import com.androiddevs.newsflash.data.network.apiwrapper.Status import com.androiddevs.newsflash.data.network.contract.NewsAPILayer -import com.androiddevs.newsflash.data.network.models.NewsResult import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest import com.androiddevs.newsflash.data.repository.contract.NewsRepository +import com.androiddevs.newsflash.data.repository.mapper.toNewsArticle +import com.androiddevs.newsflash.data.repository.models.NewsArticle import javax.inject.Inject class NewsRepositoryImpl @Inject constructor(private val apiLayer: NewsAPILayer) : NewsRepository { - override suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Response { - return apiLayer.getBusinessNews(topHeadlinesRequest) + override suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Resource> { + val response = apiLayer.getBusinessNews(topHeadlinesRequest) + if (response.status == Status.SUCCESS) { + response.data?.let { + val articles = it.articles.map { it.toNewsArticle() } + return Resource.success(articles) + } ?: run { + return Resource.getDefaultError() + } + } else { + return Resource.error(response.message, response.errorException) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/repository/contract/NewsRepository.kt b/app/src/main/java/com/androiddevs/newsflash/data/repository/contract/NewsRepository.kt index ef12e65..f4df7e4 100644 --- a/app/src/main/java/com/androiddevs/newsflash/data/repository/contract/NewsRepository.kt +++ b/app/src/main/java/com/androiddevs/newsflash/data/repository/contract/NewsRepository.kt @@ -1,9 +1,9 @@ package com.androiddevs.newsflash.data.repository.contract -import com.androiddevs.newsflash.data.network.apiwrapper.Response -import com.androiddevs.newsflash.data.network.models.NewsResult +import com.androiddevs.newsflash.data.network.apiwrapper.Resource import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest +import com.androiddevs.newsflash.data.repository.models.NewsArticle interface NewsRepository { - suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Response + suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Resource> } \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/repository/mapper/NewsArticleMapper.kt b/app/src/main/java/com/androiddevs/newsflash/data/repository/mapper/NewsArticleMapper.kt new file mode 100644 index 0000000..c4246e0 --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/data/repository/mapper/NewsArticleMapper.kt @@ -0,0 +1,16 @@ +package com.androiddevs.newsflash.data.repository.mapper + +import com.androiddevs.newsflash.data.network.models.News +import com.androiddevs.newsflash.data.repository.models.NewsArticle + +fun News.Article.toNewsArticle(): NewsArticle { + return NewsArticle( + source?.name ?: "", + author, + title, + description, + url, + urlToImage, + publishedAt ?: "" + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/androiddevs/newsflash/data/repository/models/NewsArticle.kt b/app/src/main/java/com/androiddevs/newsflash/data/repository/models/NewsArticle.kt new file mode 100644 index 0000000..68cc0fa --- /dev/null +++ b/app/src/main/java/com/androiddevs/newsflash/data/repository/models/NewsArticle.kt @@ -0,0 +1,13 @@ +package com.androiddevs.newsflash.data.repository.models + + +data class NewsArticle( + val sourceName: String, + val authorName: String = "", + val articleTitle: String, + val description: String, + val articleUrl: String, + val imageUrl: String, + val publishedDate: String = "" +) + diff --git a/app/src/main/java/com/androiddevs/newsflash/ui/adapter/HomeNewsRecyelerAdapter.kt b/app/src/main/java/com/androiddevs/newsflash/ui/adapter/HomeNewsRecyelerAdapter.kt index e91c9b5..b398319 100644 --- a/app/src/main/java/com/androiddevs/newsflash/ui/adapter/HomeNewsRecyelerAdapter.kt +++ b/app/src/main/java/com/androiddevs/newsflash/ui/adapter/HomeNewsRecyelerAdapter.kt @@ -6,11 +6,11 @@ import android.view.ViewGroup import androidx.databinding.DataBindingUtil import androidx.recyclerview.widget.RecyclerView import com.androiddevs.newsflash.R -import com.androiddevs.newsflash.data.network.models.NewsResult +import com.androiddevs.newsflash.data.network.models.News import com.androiddevs.newsflash.databinding.HomeNewsRowBinding -class HomeNewsRecyclerAdapter(private var newsList: ArrayList) : +class HomeNewsRecyclerAdapter(private var newsList: ArrayList) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { @@ -33,10 +33,11 @@ class HomeNewsRecyclerAdapter(private var newsList: ArrayList) : + data class TopHeadlinesReceived(val articleList: List) : HomeScreenStates() } \ No newline at end of file diff --git a/app/src/main/res/layout/home_news_row.xml b/app/src/main/res/layout/home_news_row.xml index 1ebf203..b68ca64 100644 --- a/app/src/main/res/layout/home_news_row.xml +++ b/app/src/main/res/layout/home_news_row.xml @@ -4,7 +4,7 @@ + type="com.androiddevs.newsflash.data.network.models.News" /> ) } } diff --git a/app/src/test/java/com/androiddevs/newsflash/data/network/NewsAPILayerImpl.kt b/app/src/test/java/com/androiddevs/newsflash/data/repository/fakes/FakeNewsAPILayerImpl.kt similarity index 60% rename from app/src/test/java/com/androiddevs/newsflash/data/network/NewsAPILayerImpl.kt rename to app/src/test/java/com/androiddevs/newsflash/data/repository/fakes/FakeNewsAPILayerImpl.kt index b098805..a2ae9fa 100644 --- a/app/src/test/java/com/androiddevs/newsflash/data/network/NewsAPILayerImpl.kt +++ b/app/src/test/java/com/androiddevs/newsflash/data/repository/fakes/FakeNewsAPILayerImpl.kt @@ -1,30 +1,29 @@ -package com.androiddevs.newsflash.data.network +package com.androiddevs.newsflash.data.repository.fakes -import com.androiddevs.newsflash.data.network.apiwrapper.Response +import com.androiddevs.newsflash.data.network.apiwrapper.Resource import com.androiddevs.newsflash.data.network.apiwrapper.Status -import com.androiddevs.newsflash.data.network.apiwrapper.handleResponse import com.androiddevs.newsflash.data.network.contract.NewsAPILayer -import com.androiddevs.newsflash.data.network.models.NewsResult +import com.androiddevs.newsflash.data.network.models.News import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest import com.androiddevs.newsflash.utils.TestableAPIStatus import com.androiddevs.newsflash.utils.loadModelFromResource -class NewsAPILayerImpl : NewsAPILayer, TestableAPIStatus { +class FakeNewsAPILayerImpl : NewsAPILayer, TestableAPIStatus { override var apiStatus = Status.SUCCESS private val successResponsePath = "news_top_headline_response/success_response.json" - override suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Response { + override suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Resource { return if (apiStatus == Status.SUCCESS) { val response = - loadModelFromResource( + loadModelFromResource( ClassLoader.getSystemClassLoader(), successResponsePath ) - Response.success(response) + Resource.success(response) } else { - Response.error("Something went wrong. Please try again later", null) + Resource.error("Something went wrong. Please try again later", null) } } diff --git a/app/src/test/java/com/androiddevs/newsflash/di/modules/FakeAppBinders.kt b/app/src/test/java/com/androiddevs/newsflash/di/modules/FakeAppBinders.kt index aa6cb27..04f4af2 100644 --- a/app/src/test/java/com/androiddevs/newsflash/di/modules/FakeAppBinders.kt +++ b/app/src/test/java/com/androiddevs/newsflash/di/modules/FakeAppBinders.kt @@ -1,7 +1,7 @@ package com.androiddevs.newsflash.di.modules -import com.androiddevs.newsflash.data.network.NewsAPILayerImpl -import com.androiddevs.newsflash.data.repository.FakeNewsRepositoryImpl +import com.androiddevs.newsflash.data.repository.fakes.FakeNewsAPILayerImpl +import com.androiddevs.newsflash.ui.viewModel.fakes.FakeNewsRepositoryImpl import com.androiddevs.newsflash.utils.DispatcherProvider import com.androiddevs.newsflash.utils.TestDispatcher import dagger.Binds @@ -22,7 +22,7 @@ abstract class FakeAppBinders { fun provideFakeRepository(): FakeNewsRepositoryImpl = FakeNewsRepositoryImpl() @Provides - fun provideFakeApiLayer(): NewsAPILayerImpl = NewsAPILayerImpl() + fun provideFakeApiLayer(): FakeNewsAPILayerImpl = FakeNewsAPILayerImpl() } } \ No newline at end of file diff --git a/app/src/test/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModelTest.kt b/app/src/test/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModelTest.kt index 065032f..7a04c2d 100644 --- a/app/src/test/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModelTest.kt +++ b/app/src/test/java/com/androiddevs/newsflash/ui/viewModel/HomeFragmentViewModelTest.kt @@ -4,7 +4,7 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.Observer import com.androiddevs.newsflash.data.network.apiwrapper.Status import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest -import com.androiddevs.newsflash.data.repository.FakeNewsRepositoryImpl +import com.androiddevs.newsflash.ui.viewModel.fakes.FakeNewsRepositoryImpl import com.androiddevs.newsflash.di.components.DaggerFakeAppComponent import com.androiddevs.newsflash.utils.DispatcherProvider import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/app/src/test/java/com/androiddevs/newsflash/data/repository/FakeNewsRepositoryImpl.kt b/app/src/test/java/com/androiddevs/newsflash/ui/viewModel/fakes/FakeNewsRepositoryImpl.kt similarity index 59% rename from app/src/test/java/com/androiddevs/newsflash/data/repository/FakeNewsRepositoryImpl.kt rename to app/src/test/java/com/androiddevs/newsflash/ui/viewModel/fakes/FakeNewsRepositoryImpl.kt index d45b2b8..382a783 100644 --- a/app/src/test/java/com/androiddevs/newsflash/data/repository/FakeNewsRepositoryImpl.kt +++ b/app/src/test/java/com/androiddevs/newsflash/ui/viewModel/fakes/FakeNewsRepositoryImpl.kt @@ -1,10 +1,10 @@ -package com.androiddevs.newsflash.data.repository +package com.androiddevs.newsflash.ui.viewModel.fakes -import com.androiddevs.newsflash.data.network.apiwrapper.Response +import com.androiddevs.newsflash.data.network.apiwrapper.Resource import com.androiddevs.newsflash.data.network.apiwrapper.Status -import com.androiddevs.newsflash.data.network.models.NewsResult import com.androiddevs.newsflash.data.network.models.TopHeadlinesRequest import com.androiddevs.newsflash.data.repository.contract.NewsRepository +import com.androiddevs.newsflash.data.repository.models.NewsArticle import com.androiddevs.newsflash.utils.TestableAPIStatus import javax.inject.Inject @@ -13,7 +13,7 @@ class FakeNewsRepositoryImpl @Inject constructor() : NewsRepository, TestableAPI override var apiStatus = Status.SUCCESS - override suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Response { + override suspend fun getBusinessNews(topHeadlinesRequest: TopHeadlinesRequest): Resource> { return if (apiStatus == Status.SUCCESS) { getSuccessResponse() } else { @@ -22,12 +22,12 @@ class FakeNewsRepositoryImpl @Inject constructor() : NewsRepository, TestableAPI } - fun getSuccessResponse(): Response { - return Response.success(NewsResult.News()) + fun getSuccessResponse(): Resource> { + return Resource.success(listOf()) } - private fun getErrorResponse(): Response { - return Response.error("", null) + private fun getErrorResponse(): Resource> { + return Resource.error("", null) } } \ No newline at end of file diff --git a/build.gradle b/build.gradle index b434b4b..fd688ed 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.72' + ext.kotlin_version = '1.4.0' repositories { google() jcenter() From 8526f0631af7eaf9ffe450ae05b388ce452ba8a8 Mon Sep 17 00:00:00 2001 From: piyushpradeepkumar Date: Sat, 22 Aug 2020 17:17:51 +0530 Subject: [PATCH 09/15] fixed build issues with coroutines --- app/build.gradle | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 400f58f..c7e7802 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,9 +5,9 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: "kotlin-kapt" + android { compileSdkVersion 30 - buildToolsVersion "29.0.3" defaultConfig { applicationId "com.androiddevs.newsflash" minSdkVersion 21 @@ -31,8 +31,13 @@ android { sourceCompatibility 1.8 targetCompatibility 1.8 } - dataBinding { - enabled = true + buildFeatures { + dataBinding = true + } + configurations.all { + resolutionStrategy { + exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug" + } } sourceSets { debug { From 9406ad2369c06251af25ad307fb6598a2ee18136 Mon Sep 17 00:00:00 2001 From: sangeetha Date: Sat, 22 Aug 2020 19:13:59 +0530 Subject: [PATCH 10/15] Initial Commit --- app/src/main/res/layout/fragment_login.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 app/src/main/res/layout/fragment_login.xml diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml new file mode 100644 index 0000000..d306062 --- /dev/null +++ b/app/src/main/res/layout/fragment_login.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file From ca9d97eb1baa018a5bf3591896e9dbac0d1600ff Mon Sep 17 00:00:00 2001 From: sangeetha Date: Sat, 22 Aug 2020 20:29:40 +0530 Subject: [PATCH 11/15] Add Login UI --- .idea/codeStyles/Project.xml | 15 +- .../main/res/drawable/button_background.xml | 9 + app/src/main/res/drawable/ic_facebook.xml | 28 ++ app/src/main/res/drawable/ic_google.xml | 85 ++++ app/src/main/res/drawable/ic_welcome.xml | 395 ++++++++++++++++++ app/src/main/res/drawable/round_button.xml | 9 + app/src/main/res/layout/fragment_login.xml | 134 +++++- app/src/main/res/values/strings.xml | 4 + 8 files changed, 663 insertions(+), 16 deletions(-) create mode 100644 app/src/main/res/drawable/button_background.xml create mode 100644 app/src/main/res/drawable/ic_facebook.xml create mode 100644 app/src/main/res/drawable/ic_google.xml create mode 100644 app/src/main/res/drawable/ic_welcome.xml create mode 100644 app/src/main/res/drawable/round_button.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 3cc336b..7a748e4 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -3,18 +3,9 @@ -