Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experiment #16

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .idea/.name

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions .idea/jarRepositories.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 15 additions & 15 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ apply plugin: 'kotlin-kapt'
apply plugin: "androidx.navigation.safeargs.kotlin"

android {
compileSdkVersion 29
compileSdkVersion 30
buildToolsVersion "29.0.3"

defaultConfig {
applicationId "com.androiddevs.mvvmnewsapp"
minSdkVersion 21
targetSdkVersion 29
targetSdkVersion 30
versionCode 1
versionName "1.0"

Expand All @@ -35,26 +35,26 @@ android {

dependencies {
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.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.71'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

// Architectural Components
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"

// Room
implementation "androidx.room:room-runtime:2.2.5"
kapt "androidx.room:room-compiler:2.2.5"
implementation "androidx.room:room-runtime:2.2.6"
kapt "androidx.room:room-compiler:2.2.6"

// Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:2.2.5"
implementation "androidx.room:room-ktx:2.2.6"

// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5'

// Coroutine Lifecycle Scopes
Expand All @@ -67,8 +67,8 @@ dependencies {
implementation "com.squareup.okhttp3:logging-interceptor:4.5.0"

// Navigation Components
implementation "androidx.navigation:navigation-fragment-ktx:2.2.1"
implementation "androidx.navigation:navigation-ui-ktx:2.2.1"
implementation "androidx.navigation:navigation-fragment-ktx:2.3.2"
implementation "androidx.navigation:navigation-ui-ktx:2.3.2"

// Glide
implementation 'com.github.bumptech.glide:glide:4.11.0'
Expand Down
8 changes: 5 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androiddevs.mvvmnewsapp">

<uses-permission android:name="android.permission.INTERNET"/>

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:icon="@drawable/logo"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:roundIcon="@drawable/logo"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<activity android:name=".ui.NewsActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand Down
Binary file added app/src/main/ic_launcher-playstore.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 65 additions & 0 deletions app/src/main/java/adapters/NewsAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package adapters

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.androiddevs.mvvmnewsapp.R
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.item_article_preview.view.*
import models.Article

class NewsAdapter: RecyclerView.Adapter<NewsAdapter.ArticleViewHolder>() {

inner class ArticleViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)

private val differCallback = object : DiffUtil.ItemCallback<Article>(){

override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem.url == newItem.url
}

override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem == newItem
}
}

val differ = AsyncListDiffer(this, differCallback)

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
return ArticleViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_article_preview,
parent,
false
)
)
}

override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
val article = differ.currentList[position]
holder.itemView.apply {
Glide.with(this).load(article.urlToImage).into(ivArticleImage)
tvSource.text = article.source.name
tvTitle.text = article.title
tvDescription.text = article.description
tvPublishedAt.text = article.publishedAt
setOnClickListener{
onItemClickListener?.let{ it(article) }
}
}
}

override fun getItemCount(): Int {
return differ.currentList.size
}

private var onItemClickListener:((Article) -> Unit)? = null

fun setOnItemClickListener(listener: (Article) -> Unit){
onItemClickListener = listener

}
}
20 changes: 20 additions & 0 deletions app/src/main/java/api/NewsAPI.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package api

import models.NewsResponse
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Query
import util.Constants.Companion.API_KEY

interface NewsAPI {

@GET("v2/top-headlines")
suspend fun getBreakingNews(
@Query("country")
countryCode : String = "id",
@Query("page")
pageNumber:Int = 1,
@Query("apiKey")
apiKey: String = API_KEY
) : Response<NewsResponse>
}
22 changes: 22 additions & 0 deletions app/src/main/java/api/RetrofitInstance.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package api

import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import util.Constants.Companion.BASE_URL

class RetrofitInstance {
companion object{
private val retrofit by lazy {
val logging = HttpLoggingInterceptor()
logging.setLevel(HttpLoggingInterceptor.Level.BODY)
val client = OkHttpClient.Builder().addInterceptor(logging).build()
Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create()).client(client).build()
}

val api: NewsAPI by lazy {
retrofit.create(NewsAPI::class.java)
}
}
}
12 changes: 0 additions & 12 deletions app/src/main/java/com/androiddevs/mvvmnewsapp/MainActivity.kt

This file was deleted.

22 changes: 22 additions & 0 deletions app/src/main/java/com/androiddevs/mvvmnewsapp/ui/NewsAcitivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.androiddevs.mvvmnewsapp.ui

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import com.androiddevs.mvvmnewsapp.R
import repository.NewsRepository

class NewsActivity : AppCompatActivity() {

lateinit var viewModel: NewsViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_news)

val newsRepository = NewsRepository()
val viewModelProviderFactory = NewsViewModelProviderFactory(newsRepository)
viewModel = ViewModelProvider(this, viewModelProviderFactory).get(NewsViewModel::class.java)

}
}
41 changes: 41 additions & 0 deletions app/src/main/java/com/androiddevs/mvvmnewsapp/ui/NewsViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.androiddevs.mvvmnewsapp.ui

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import models.NewsResponse
import repository.NewsRepository
import retrofit2.Response
import util.Resource

class NewsViewModel(

private val newsRepository: NewsRepository

) : ViewModel() {

val breakingNews: MutableLiveData<Resource<NewsResponse>> = MutableLiveData()
private var breakingNewsPage = 1

init {
getBreakingNews("id")
}

private fun getBreakingNews(countryCode: String) = viewModelScope.launch {
breakingNews.postValue(Resource.Loading())
val response = newsRepository.getBreakingNews(countryCode, breakingNewsPage)
breakingNews.postValue(handleBreakingNewsResponse(response))
}

private fun handleBreakingNewsResponse(response: Response<NewsResponse>) : Resource<NewsResponse> {
if(response.isSuccessful){
response.body()?.let { resultResponse ->
return Resource.Success(resultResponse)
}
}

return Resource.Error(response.message())
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.androiddevs.mvvmnewsapp.ui

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import repository.NewsRepository

class NewsViewModelProviderFactory(
private val newsRepository : NewsRepository
) : ViewModelProvider.Factory {

override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return NewsViewModel(newsRepository) as T
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.androiddevs.mvvmnewsapp.ui.fragments

import android.os.Bundle
import android.view.View
import android.webkit.WebViewClient
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.navArgs
import com.androiddevs.mvvmnewsapp.R
import com.androiddevs.mvvmnewsapp.ui.NewsActivity
import com.androiddevs.mvvmnewsapp.ui.NewsViewModel
import kotlinx.android.synthetic.main.fragment_article.*

class ArticleFragment: Fragment(R.layout.fragment_article) {

private lateinit var viewModel: NewsViewModel
private val args: ArticleFragmentArgs by navArgs()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = (activity as NewsActivity).viewModel
val article = args.article
webView.apply {
webViewClient = WebViewClient()
loadUrl(article.url)
}
}
}
Loading