티스토리 뷰
build.gradle (Module: app)
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
// Retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.7.1'
implementation 'com.squareup.retrofit2:converter-gson:2.7.1'
implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
// OkHttp3
implementation 'com.squareup.okhttp3:logging-interceptor:4.3.1'
// Kotlin Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.4'
}
회원가입 후 로그인
뉴스 정보를 읽어올 때 필요한 API key
https://newsapi.org/v2/everything?q=Nigeria&sortBy=publishedAt&apiKey=********************************
입력 시 Json 형식의 데이터가 나온다.
{
"status": "ok",
"totalResults": 9388,
"articles": [
{
"source": {
"id": null,
"name": "Vanguard"
},
"author": "David O Royal",
"title": "Adeboye, Martins, PFN, others welcome re-opening of worship centres",
"description": "By Sam Eyoboka GENERAL Overseer of the Redeemed Christian Church of God, RCCG, Pastor Enoch Adejare Adeboye, Catholic Archbishop of Lagos, Most Rev. Alfred Adewale Martins, the Lagos State branch of the Pentecostal Fellowship of Nigeria, PFN were the first se…",
"url": "https://www.vanguardngr.com/2020/08/adeboye-martins-pfn-others-welcome-re-opening-of-worship-centres/",
"urlToImage": "https://i2.wp.com/www.vanguardngr.com/wp-content/uploads/2020/03/Catholic-Church.jpg?fit=1920%2C1280&ssl=1",
"publishedAt": "2020-08-02T13:43:17Z",
"content": "An empty church\r\nBy Sam Eyoboka\r\nGENERAL Overseer of the Redeemed Christian Church of God, RCCG, Pastor Enoch Adejare Adeboye, Catholic Archbishop of Lagos, Most Rev. Alfred Adewale Martins, the Lago… [+4874 chars]"
},
...
{
"source": {
"id": null,
"name": "Vanguard"
},
"author": "Oboh",
"title": "I’m ashamed people misinterpret ‘waiving sovereignty’ in $500m Chinese loan, says Amaechi",
"description": "Minister of Transportation, Rotimi Amaechi, has said he is ashamed that some people can’t understand that the clause ‘waiving sovereignty’ in the loan agreement between Nigeria and China is only a contract term, a sovereign guarantee that assures payback acco…",
"url": "https://www.vanguardngr.com/2020/08/im-ashamed-people-misinterprete-waiving-sovereignty-in-500m-chinese-loan-says-amaechi/",
"urlToImage": "https://i0.wp.com/www.vanguardngr.com/wp-content/uploads/2019/02/Vanguard_Live_Backdrop.jpg?fit=1920%2C1080&ssl=1",
"publishedAt": "2020-08-02T11:32:14Z",
"content": "Minister of Transportation, Rotimi Amaechi, has said he is ashamed that some people cant understand that the clause waiving sovereignty in the loan agreement between Nigeria and China is only a contr… [+4112 chars]"
}
]
}
Json 형식으로 된 데이터를 자바 POJO 클래스로 변환 필요
Json 형식으로 된 데이터를 복사한 후
www.jsonschema2pojo.org 에 들어가서 붙여넣기
버튼 클릭 후
Gson 형식의 POJO 클래스로 변환 성공
LatestNews.kt
package com.jwsoft.kotlinproject
import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName
data class LatestNews (
@SerializedName("status")
@Expose
val status: String,
@SerializedName("totalResults")
@Expose
val totalResults: Int,
@SerializedName("articles")
@Expose
val articles: List<Article>
)
Article.kt
package com.jwsoft.kotlinproject
import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName
data class Article (
@SerializedName("source")
@Expose
val source: Source,
@SerializedName("author")
@Expose
val author: String,
@SerializedName("title")
@Expose
val title: String,
@SerializedName("description")
@Expose
val description: String,
@SerializedName("url")
@Expose
val url: String,
@SerializedName("urlToImage")
@Expose
val urlToImage: String,
@SerializedName("publishedAt")
@Expose
val publishedAt: String,
@SerializedName("content")
@Expose
val content: String
)
Source.kt
package com.jwsoft.kotlinproject
import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName
data class Source (
@SerializedName("id")
@Expose
val id: Any,
@SerializedName("name")
@Expose
val name: String
)
NewsApiService.kt
package com.jwsoft.kotlinproject
import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object NewsApiService {
//creating a Network Interceptor to add api_key in all the request as authInterceptor
private val interceptor = Interceptor { chain ->
val url = chain.request().url.newBuilder().addQueryParameter("apiKey", "API_KEY").build()
val request = chain.request()
.newBuilder()
.url(url)
.build()
chain.proceed(request)
}
// we are creating a networking client using OkHttp and add our authInterceptor.
private val apiClient = OkHttpClient().newBuilder().addInterceptor(interceptor).build()
private fun getRetrofit(): Retrofit {
return Retrofit.Builder().client(apiClient)
.baseUrl("https://newsapi.org/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
}
val newsApi: NewsApiInterface = getRetrofit().create(NewsApiInterface::class.java)
}
Singleton Pattern 사용.
API 요청 시 interceptor 변수가 파라미터 쿼리로 API_KEY 를 추가해서 보냄.
.addCallAdapterFactory(CoroutineCallAdapterFactory()) 는
Deferred<Response<LatestNews>> 리턴타입을 사용하기 위함.
참고
github.com/JakeWharton/retrofit2-kotlin-coroutines-adapter
.addCallAdapterFactory(CoroutineCallAdapterFactory()) 없이
suspend 추가 시 Response<LatestNews> 로 사용 가능.
github.com/square/retrofit/blob/master/CHANGELOG.md#version-260-2019-06-05
NewsApiService.kt
build.gradle (Module: app)
android {
kotlinOptions {
jvmTarget = "1.8"
}
}
추가 시 에러 해결.
NewsApiInterface.kt
package com.jwsoft.kotlinproject
import kotlinx.coroutines.Deferred
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Query
interface NewsApiInterface {
//fetches latest news with the required query params
@GET("v2/everything")
fun fetchLatestNewsAsync(@Query("q") query: String,
@Query("sortBy") sortBy : String) : Deferred<Response<LatestNews>>
}
.addCallAdapterFactory(CoroutineCallAdapterFactory()) 추가 시
Deferred<Response<LatestNews>> 리턴타입을 사용.
package com.jwsoft.kotlinproject
import kotlinx.coroutines.Deferred
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Query
interface NewsApiInterface {
//fetches latest news with the required query params
@GET("v2/everything")
suspend fun fetchLatestNewsAsync(@Query("q") query: String,
@Query("sortBy") sortBy : String) : Response<LatestNews>
}
.addCallAdapterFactory(CoroutineCallAdapterFactory()) 없이
suspend 추가 시 Response<LatestNews> 로 사용 가능.
코루틴이 사용된 이유는 해당 API 가 응답이 올 때까지 기다리게 하기 위함.
Output.kt
package com.jwsoft.kotlinproject
import java.lang.Exception
sealed class Output<out T : Any> {
data class Success<out T : Any>(val output : T) : Output<T>()
data class Error(val exception: Exception) : Output<Nothing>()
}
sealed class 사용
BaseRepository.kt
package com.jwsoft.kotlinproject
import android.util.Log
import retrofit2.Response
import java.io.IOException
open class BaseRepository {
suspend fun <T : Any> safeApiCall(call : suspend()-> Response<T>, error : String) : T?{
val result = newsApiOutput(call, error)
var output : T? = null
when(result){
is Output.Success ->
output = result.output
is Output.Error -> Log.e("Error", "The $error and the ${result.exception}")
}
return output
}
private suspend fun<T : Any> newsApiOutput(call: suspend()-> Response<T> , error: String) : Output<T>{
val response = call.invoke()
return if (response.isSuccessful)
Output.Success(response.body()!!)
else
Output.Error(IOException("OOps .. Something went wrong due to $error"))
}
}
NewRepo.kt
package com.jwsoft.kotlinproject
class NewsRepo(private val apiInterface: NewsApiInterface) : BaseRepository() {
//get latest news using safe api call
suspend fun getLatestNews() : MutableList<Article>?{
return safeApiCall(
//await the result of deferred type
call = {apiInterface.fetchLatestNewsAsync("Nigeria", "publishedAt").await()},
error = "Error fetching news"
//convert to mutable list
)?.articles?.toMutableList()
}
}
NewRepo 클래스는 safeApiCall() 함수를 호출하기 위해 BaseRepository 클래스를 상속한다.
파라미터로 NewsApiInterface 를 사용한다.
safeApiCall() 에 파라미터를 넣고 호출하면 await() 를 사용하여 NewsApiInterface 의 결과를 기다린다.
이게 가능한 이유는 fetchLastestNews() 함수의 리턴 타입이 코루틴의 Deffered 타입이기 때문이다.
NewsViewModel.kt
package com.jwsoft.kotlinproject
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
class NewsViewModel : ViewModel() {
//create a new Job
private val parentJob = Job()
//create a coroutine context with the job and the dispatcher
private val coroutineContext : CoroutineContext get() = parentJob + Dispatchers.Default
//create a coroutine scope with the coroutine context
private val scope = CoroutineScope(coroutineContext)
//initialize news repo
private val newsRepository : NewsRepo = NewsRepo(NewsApiService.newsApi)
//live data that will be populated as news updates
val newsLiveData = MutableLiveData<MutableList<Article>>()
fun getLatestNews() {
///launch the coroutine scope
scope.launch {
//get latest news from news repo
val latestNews = newsRepository.getLatestNews()
//post the value inside live data
newsLiveData.postValue(latestNews)
}
}
fun cancelRequests() = coroutineContext.cancel()
}
NewsViewModelFactory.kt
package com.jwsoft.kotlinproject
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class NewsViewModelFactory : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return NewsViewModel() as T
}
}
MainActivity.kt
package com.jwsoft.kotlinproject
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.*
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class MainActivity : AppCompatActivity() {
private lateinit var newsViewModel: NewsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// create instance of view model factory
val viewModelFactory = NewsViewModelFactory()
// Use view ModelFactory to initialize view model
newsViewModel = ViewModelProvider(this@MainActivity, viewModelFactory).get(NewsViewModel::class.java)
newsViewModel.getLatestNews()
newsViewModel.newsLiveData.observe(this, Observer {
//bind your ui here
Log.e("news count", it.size.toString())
Log.e("author", it[0].author)
Log.e("title", it[0].title)
})
btnRefresh.setOnClickListener {
newsViewModel.getLatestNews()
}
}
}
참고
'Android > Kotlin' 카테고리의 다른 글
[Kotlin] ImageView 타원화 (0) | 2020.08.04 |
---|---|
[Kotlin] CoordinatorLayout + ViewPager2 + TabLayout (0) | 2020.08.03 |
[Kotlin] import com.google.gson.* (0) | 2020.07.31 |
[Kotlin] import org.json.* (0) | 2020.07.31 |
[Kotlin] MVVM + Coroutine (0) | 2020.07.30 |
- Total
- Today
- Yesterday
- 자바
- JSONArray
- coroutine
- Vue.js #Vue.js + javascript
- Intent
- Design Pattern
- ViewModel
- James Kim
- Architecture Pattern
- handler
- MVVM
- 안드로이드 #코틀린 #Android #Kotlin
- View
- 혀가 길지 않은 개발자
- JSONObject
- recyclerview
- 안드로이드
- XML
- 코틀린
- ArrayList
- ViewPager2
- DataBinding
- TabLayout
- activity
- java
- Livedata
- Android
- fragment
- Kotlin
- CoordinatorLayout
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |