Doing multiple RxJava calls and using result for next call - android

I'm trying to use RxJava to solve this problem. I have 3 calls that need to be executed after each other, using the result of the last call for the next call.
For sake of simplicity I've hosted 3 files on my server that are the 3 calls:
http://jimclermonts.nl/booky/step1user
http://jimclermonts.nl/booky/step2cookie
http://jimclermonts.nl/booky/step3token
What is the correct and cleanest way to do this?
build.gradle:
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.3.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
implementation 'com.squareup.okhttp3:okhttp:3.9.0'
// reactive extensions
implementation "com.squareup.retrofit2:adapter-rxjava2:2.3.0"
implementation 'io.reactivex.rxjava2:rxjava:2.1.7'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
MainActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val result = SomeWebService().getToken()
if (result != null) {
result.doOnComplete {
//how to get the token value?
}
}
}
SomeWebService
class SomeWebService {
lateinit var apiService: ApiService
var tokenResult: Observable<String>? = null
fun getToken() : Observable<String>? {
if (tokenResult == null) {
apiService = ApiService.retrofit.create(ApiService::class.java)
val body = step1ApiUserResponse()
val cookie = step2CookieResponse(body.blockingSingle())
val tokenResult = step3TokenResponse(cookie.blockingSingle())
this.tokenResult = tokenResult
tokenResult.doOnComplete { }
} else {
tokenResult!!.doOnComplete { }
}
return tokenResult
}
fun step1ApiUserResponse(): Observable<String> {
return Observable.create {
apiService.getStep1User()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess { user ->
val body = user.getResponse()
if (body != null) {
it.onNext(body)
}
}
.doOnError {
it.printStackTrace()
}
}
}
fun step2CookieResponse(body: String): Observable<String> {
return Observable.create {
apiService.getStep2Cookie(body)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess { result ->
val bodyResult = result.body().toString()
it.onNext(bodyResult)
}
}
}
fun step3TokenResponse(cookie: String): Observable<String> {
return Observable.create {
apiService.getStep3Token(cookie)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess { result ->
val body = result.getToken()
if (body != null) {
it.onNext(body)
}
}
}
}
}
ApiService:
interface ApiService {
#GET("/booky/step1user")
fun getStep1User(): Single<UserResponse>
#GET("/booky/step2cookie")
fun getStep2Cookie(body: String): Single<Response>
#GET("/booky/step3token")
fun getStep3Token(#Header("Cookie") sessionId: String): Single<TokenResponse>
companion object {
val interceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
val client = OkHttpClient.Builder()
.addInterceptor(interceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl("http://jimclermonts.nl")
.addConverterFactory(MoshiConverterFactory.create().asLenient())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(client)
.build()
}
}
TokenResponse:
class TokenResponse {
#Json(name = "Token")
private var token: String? = null
fun getToken(): String? {
return token
}
fun setToken(token: String) {
this.token = token
}
}
UserResponse:
class UserResponse {
#Json(name = "Response")
private var response: String? = null
fun getResponse(): String? {
return response
}
fun setResponse(response: String) {
this.response = response
}
}

MainActivity:
val service = SomeWebService()
service.getToken()
.subscribe(
{ token ->
Log.d("TOKEN", token)
},
{e ->
Log.e("Token error", e.localizedMessage)
}
)
SomeWebService:
class SomeWebService {
lateinit var apiService: ApiService
var tokenResult: Observable<String>? = null
fun getToken(): Observable<String> {
apiService = ApiService.retrofit.create(ApiService::class.java)
return step1ApiUserResponse()
.flatMap { body ->
step2CookieResponse(body)
.flatMap { cookie ->
step3TokenResponse(cookie)
}
}
}
fun step1ApiUserResponse(): Observable<String?> {
return apiService.getStep1User()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map { user ->
Log.d("Step1", user.toString())
user.getResponse()
}
.toObservable()
}
fun step2CookieResponse(body: String): Observable<String?> {
return apiService.getStep2Cookie(body)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map { result ->
result.getCookie()
}
.toObservable()
}
fun step3TokenResponse(cookie: String): Observable<String?> {
return apiService.getStep3Token(cookie)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map { result ->
result.getToken()
}
.toObservable()
}
}

Related

NewsApi is not showing Error message (Android)(Kotlin)

I am having trouble getting the Error messages when something goes wrong with the Response from the NewsApi. Everything works fine except for that. For example when the maximum amount of request is made for my API KEY no error message will show up except for the text I wrote.
Anybody experienced this before? I tried to use Response.errorBody() instead of Response.message() in the ViewModel however what came out was unintelligible jargon.
//Api Get Request
interface NewsApi {
#GET("/v2/top-headlines")
suspend fun getNews(#Query("country") country : String, #Query("category") category : String?,#Query("page") pageNumber: Int = 1, #Query("apiKey") key : String = api_key) : Response<NewsResponse>
}
//Creating Retrofit instance and HTTP-logging-interceptors
object RetrofitHelper {
private const val BASE_URL = "https://newsapi.org/v2/"
private fun httpclient():OkHttpClient{
val builder = OkHttpClient.Builder()
builder.addInterceptor(HttpLoggingInterceptor().apply {
setLevel(HttpLoggingInterceptor.Level.BODY)
builder.addInterceptor(
ChuckerInterceptor.Builder(application_class.context)
.collector(ChuckerCollector(application_class.context))
.maxContentLength(250000L)
.redactHeaders(emptySet())
.alwaysReadResponseBody(false)
.build()
)
})
return builder.build()
}
fun getInstance(): Retrofit {
return Retrofit.Builder()
.client(httpclient())
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
//Singleton to call the API
object NewsApiCall {
val api by lazy {
RetrofitHelper.getInstance().create(NewsApi::class.java)
}
}
Repository
class NewsRepository(val db:ArticleDatabase ) {
suspend fun getBreakingNews(countryCode: String, category:String) =
NewsApiCall.api.getNews(countryCode, category)
}
ViewModel
class viewModel(var newsRepository:NewsRepository):ViewModel() {
val breakingNews: MutableLiveData<Resource<NewsResponse>> = MutableLiveData()
var breakingNewsResponse: NewsResponse? = null
fun getBreakingNews(countryCode: String,category:String) = viewModelScope.launch {
breakingNews.postValue(Resource.Loading())
val response = newsRepository.getBreakingNews(countryCode, category)
breakingNews.postValue(handleBreakingNewsResponse(response))
}
private fun handleBreakingNewsResponse(response: Response<NewsResponse>): Resource<NewsResponse> {
if (response.isSuccessful) {
response.body()?.let { resultResponse ->
if (breakingNewsResponse == null) {
breakingNewsResponse = resultResponse
}
else {
val olArticles = breakingNewsResponse?.articles
val newArticles = resultResponse.articles
olArticles?.addAll(newArticles)
}
return Resource.Success(breakingNewsResponse ?: resultResponse)
}
}
return Resource.Error(response.errorBody().toString())
}
}
MainActivity
,Calling the APIs from the MainActivity
ViewModel.getBreakingNews("us", GENERAL)
BreakingNewsFragment, Displaying of the UI
fun LoadingData_forRecyclerView(){
viewModel.breakingNews.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { newsResponse ->
RefreshData = newsResponse
Newsadapters.differ.submitList(newsResponse.articles.toList())
ProgressBar.visibility = View.INVISIBLE
LoadingTextView.visibility = View.INVISIBLE
ErrorImage.visibility = View.GONE
}
}
is Resource.Error -> {
response.message?.let { message ->
Log.e("Error", "An error accrued: $message")
ProgressBar.visibility = View.INVISIBLE
LoadingTextView.visibility = View.VISIBLE
LoadingTextView.setText("The following error has occured $message!")
}
}
is Resource.Loading -> {
Log.e("Loading", "Loading")
ProgressBar.visibility = View.VISIBLE
LoadingTextView.visibility = View.VISIBLE
ErrorImage.visibility = View.GONE
}
}
})
}
Resource, The utility resource class
sealed class Resource<T>(
val data: T? = null,
val message: String? = null
) {
class Success<T>(data: T) : Resource<T>(data)
class Error<T>(message: String, data: T? = null) : Resource<T>(data, message)
class Loading<T> : Resource<T>()
}

How to until wait 2 parallel retrofit calls both finish?

I want to call 2 retrofit services in parallel and then do an action only when both of them finished, but I don't seem to figuer it out how.
I have a viewModel where I have defined my services:
var config= List<Configuration>
fun getClientProducts() {
getClientClientConfigUseCase
.build(this)
.executeWithError({ config ->
config = config
}, {
})
}
var therapies = List<DtoTherapy>
fun getTherapies() {
getTherapiesUseCase
.build(this)
.executeWithError({ config ->
therapies = it
}, {
})
}
And then I want to call both services in parallel in my fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupUi(view)
loadUserData()
viewModel.getClientProducts()
viewModel.getTherapies()
}
And when both variables config and therapies have the value do an action. But as I said maybe one service take 1 sec to respond and another 4 secs, and I want only to perfom an action when both have finished. Any help with be appreciated.
Here is the class I use to build the use case call:
abstract class SingleUseCase<T> : UseCase() {
private lateinit var single: Single<T>
private lateinit var useCaseInterface: UseCaseInterface
private var withLoader: Boolean = false
private var withErrorMessage: Boolean = false
internal abstract fun buildUseCaseSingle(): Single<T>
fun build(useCaseInterface: UseCaseInterface): SingleUseCase<T> {
this.withLoader = false
this.withErrorMessage = false
this.useCaseInterface = useCaseInterface
this.single = buildUseCaseSingle()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doAfterSuccess { useCaseInterface.onSuccess(it) }
return this
}
fun withLoader(): SingleUseCase<T> {
this.withLoader = true
return this
}
fun withErrorMessage(): SingleUseCase<T> {
this.withErrorMessage = true
return this
}
fun single(): Single<T> {
return this.single
}
fun execute(onSuccess: ((t: T) -> Unit)) {
useCaseInterface.onPrepareRequest(withLoader)
buildObservable(onSuccess)
}
private fun buildObservable(onSuccess: ((t: T) -> Unit)) {
disposeLast()
lastDisposable = single
.doFinally { useCaseInterface.onFinishRequest(this.withLoader) }
.subscribe(
{ onSuccess(it) },
{
useCaseInterface.onError(mapError(it), withErrorMessage)
})
lastDisposable?.let {
compositeDisposable.add(it)
}
}
fun executeWithError(onSuccess: ((success: T) -> Unit), onError: ((error: ApiError ) -> Unit)) {
useCaseInterface.onPrepareRequest(withLoader)
buildObservable(onSuccess, onError)
}
private fun buildObservable(onSuccess: ((success: T) -> Unit), onError: ((error: ApiError ) -> Unit)) {
disposeLast()
lastDisposable = single
.doFinally { useCaseInterface.onFinishRequest(this.withLoader) }
.subscribe(
{ onSuccess(it) },
{
onError(mapError(it))
useCaseInterface.onError(mapError(it), withErrorMessage)
})
lastDisposable?.let {
compositeDisposable.add(it)
}
}
private fun mapError(t: Throwable): ApiError {
return if(t is HttpException) {
val apiError = t.response()?.errorBody()?.string()
try {
ApiError (t.code(), t.response()?.errorBody()?.string(), Gson().fromJson(apiError, GenericError::class.java))
} catch(e: Exception) {
ApiError (-2, "Unkown error")
}
} else ApiError (-1, "Unkown error")
}
}
And this is a specific usecase class:
class GetClientConfigUseCase #Inject constructor(private val repository: UserRepository) :
SingleUseCase<ClientConfigResponse>() {
override fun buildUseCaseSingle(): Single<ClientConfigResponse> {
return repository.getUserConfig()
}
}
I guess you need zip operation. With zip operation you can have a result of two observable in one place when both of them received data.
Observable<List<ClientProducts>> observable1 = ...
Observable<List<DtoTherapy>> observable2 = ...
Observable.zip(observable1, observable2, new BiFunction<List<ClientProducts>, List<DtoTherapy>, Result>() {
#Override
public Result apply(List<ClientProducts> products, List<DtoTherapy> therapies) throws Exception
{
// here you have both of your data
// do operations on products and therapies
// then return the result
return result;
}
});

Make android MVVM, Kotlin Coroutines and Retrofit 2.6 work asynchronously

I've just finished my first Android App. It works as it should but, as you can imagine, there's a lot of spaghetti code and lack of performance. From what I've learned on Android and Kotlin language making this project (and a lot of articles/tutorials/SO answers) I'm trying to start it again from scratch to realize a better version. For now I'd like to keep it as simple as possible, just to better understand how to handle API calls with Retrofit and MVVM pattern, so no Volley/RXjava/Dagger etc.
I'm starting from the login obviously; I would like to make a post request to simply compare the credentials, wait for the response and, if positive, show a "loading screen" while fetching and processing data to show in the home page. I'm not storing any information so I have realized a singleton class that holds data as long as the app is running (btw, is there another way to do it?).
RetrofitService
private val retrofitService = Retrofit.Builder()
.addConverterFactory(
GsonConverterFactory
.create(
GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.setLenient().setDateFormat("yyyy-MM-dd")
.create()
)
)
.addConverterFactory(RetrofitConverter.create())
.baseUrl(BASE_URL)
.build()
`object ApiObject {
val retrofitService: ApiInterface by lazy {
retrofitBuilder.create(ApiInterface::class.java) }
}
ApiInterface
interface ApiInterface {
#GET("workstation/{date}")
suspend fun getWorkstations(
#Path("date") date: Date
): List<Workstation>
#GET("reservation/{date}")
suspend fun getReservations(
#Path("date") date: Date
): List<Reservation>
#GET("user")
suspend fun getUsers(): List<User>
#GET("user/login")
suspend fun validateLoginCredentials(
#Query("username") username: String,
#Query("password") password: String
): Response<User>
ApiResponse
sealed class ApiResponse<T> {
companion object {
fun <T> create(response: Response<T>): ApiResponse<T> {
return if(response.isSuccessful) {
val body = response.body()
// Empty body
if (body == null || response.code() == 204) {
ApiSuccessEmptyResponse()
} else {
ApiSuccessResponse(body)
}
} else {
val msg = response.errorBody()?.string()
val errorMessage = if(msg.isNullOrEmpty()) {
response.message()
} else {
msg.let {
return#let JSONObject(it).getString("message")
}
}
ApiErrorResponse(errorMessage ?: "Unknown error")
}
}
}
}
class ApiSuccessResponse<T>(val data: T): ApiResponse<T>()
class ApiSuccessEmptyResponse<T>: ApiResponse<T>()
class ApiErrorResponse<T>(val errorMessage: String): ApiResponse<T>()
Repository
class Repository {
companion object {
private var instance: Repository? = null
fun getInstance(): Repository {
if (instance == null)
instance = Repository()
return instance!!
}
}
private var singletonClass = SingletonClass.getInstance()
suspend fun validateLoginCredentials(username: String, password: String) {
withContext(Dispatchers.IO) {
val result: Response<User>?
try {
result = ApiObject.retrofitService.validateLoginCredentials(username, password)
when (val response = ApiResponse.create(result)) {
is ApiSuccessResponse -> {
singletonClass.loggedUser = response.data
}
is ApiSuccessEmptyResponse -> throw Exception("Something went wrong")
is ApiErrorResponse -> throw Exception(response.errorMessage)
}
} catch (error: Exception) {
throw error
}
}
}
suspend fun getWorkstationsListFromService(date: Date) {
withContext(Dispatchers.IO) {
val workstationsListResult: List<Workstation>
try {
workstationsListResult = ApiObject.retrofitService.getWorkstations(date)
singletonClass.rWorkstationsList.postValue(workstationsListResult)
} catch (error: Exception) {
throw error
}
}
}
suspend fun getReservationsListFromService(date: Date) {
withContext(Dispatchers.IO) {
val reservationsListResult: List<Reservation>
try {
reservationsListResult = ApiObject.retrofitService.getReservations(date)
singletonClass.rReservationsList.postValue(reservationsListResult)
} catch (error: Exception) {
throw error
}
}
}
suspend fun getUsersListFromService() {
withContext(Dispatchers.IO) {
val usersListResult: List<User>
try {
usersListResult = ApiObject.retrofitService.getUsers()
singletonClass.rUsersList.postValue(usersListResult.let { usersList ->
usersList.filterNot { user -> user.username == "admin" }
.sortedWith(Comparator { x, y -> x.surname.compareTo(y.surname) })
})
} catch (error: Exception) {
throw error
}
}
}
SingletonClass
const val FAILED = 0
const val COMPLETED = 1
const val RUNNING = 2
class SingletonClass private constructor() {
companion object {
private var instance: SingletonClass? = null
fun getInstance(): SingletonClass {
if (instance == null)
instance = SingletonClass()
return instance!!
}
}
//User
var loggedUser: User? = null
//Workstations List
val rWorkstationsList = MutableLiveData<List<Workstation>>()
//Reservations List
val rReservationsList = MutableLiveData<List<Reservation>>()
//Users List
val rUsersList = MutableLiveData<List<User>>()
}
ViewModel
class ViewModel : ViewModel() {
private val singletonClass = SingletonClass.getInstance()
private val repository = Repository.getInstance()
//MutableLiveData
//Login
private val _loadingStatus = MutableLiveData<Boolean>()
val loadingStatus: LiveData<Boolean>
get() = _loadingStatus
private val _successfulAuthenticationStatus = MutableLiveData<Boolean>()
val successfulAuthenticationStatus: LiveData<Boolean>
get() = _successfulAuthenticationStatus
//Data fetch
private val _listsLoadingStatus = MutableLiveData<Int>()
val listsLoadingStatus: LiveData<Int>
get() = _listsLoadingStatus
private val _errorMessage = MutableLiveData<String>()
val errorMessage: LiveData<String>
get() = _errorMessage
fun onLoginClicked(username: String, password: String) {
launchLoginAuthentication {
repository.validateLoginCredentials(username, password)
}
}
private fun launchLoginAuthentication(block: suspend () -> Unit): Job {
return viewModelScope.launch {
try {
_loadingStatus.value = true
block()
} catch (error: Exception) {
_errorMessage.postValue(error.message)
} finally {
_loadingStatus.value = false
if (singletonClass.loggedUser != null)
_successfulAuthenticationStatus.value = true
}
}
}
fun onLoginPerformed() {
val date = Calendar.getInstance().time
launchListsFetch {
//how to start these all at the same time? Then wait until their competion
//and call the two methods below?
repository.getReservationsListFromService(date)
repository.getWorkstationsListFromService(date)
repository.getUsersListFromService()
}
}
private fun launchListsFetch(block: suspend () -> Unit): Job {
return viewModelScope.async {
try {
_listsLoadingStatus.value = RUNNING
block()
} catch (error: Exception) {
_listsLoadingStatus.value = FAILED
_errorMessage.postValue(error.message)
} finally {
//I'd like to perform these operations at the same time
prepareWorkstationsList()
prepareReservationsList()
//and, when both completed, set this value
_listsLoadingStatus.value = COMPLETED
}
}
}
fun onToastShown() {
_errorMessage.value = null
}
}
LoginActivity
class LoginActivity : AppCompatActivity() {
private val viewModel: LoginViewModel
get() = ViewModelProviders.of(this).get(LoginViewModel::class.java)
private val loadingFragment = LoadingDialogFragment()
var username = ""
var password = ""
private lateinit var loginButton: Button
lateinit var context: Context
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
loginButton = findViewById(R.id.login_button)
loginButton.setOnClickListener {
username = login_username.text.toString().trim()
password = login_password.text.toString().trim()
viewModel.onLoginClicked(username, password.toMD5())
}
viewModel.loadingStatus.observe(this, Observer { value ->
value?.let { show ->
progress_bar_login.visibility = if (show) View.VISIBLE else View.GONE
}
})
viewModel.successfulAuthenticationStatus.observe(this, Observer { successfullyLogged ->
successfullyLogged?.let {
loadingFragment.setStyle(DialogFragment.STYLE_NORMAL, R.style.CustomLoadingDialogFragment)
if (successfullyLogged) {
loadingFragment.show(supportFragmentManager, "loadingFragment")
viewModel.onLoginPerformed()
} else {
login_password.text.clear()
login_password.isFocused
password = ""
}
}
})
viewModel.listsLoadingStatus.observe(this, Observer { loadingResult ->
loadingResult?.let {
when (loadingResult) {
COMPLETED -> {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
setResult(Activity.RESULT_OK)
finish()
}
FAILED -> {
loadingFragment.changeText("Error")
loadingFragment.showProgressBar(false)
loadingFragment.showRetryButton(true)
}
}
}
})
viewModel.errorMessage.observe(this, Observer { value ->
value?.let { message ->
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
viewModel.onToastShown()
}
})
}
Basically what I'm trying to do is to send username and password, show a progress bar while waiting for the result (if successful the logged user object is returned, otherwise a toast with the error message is shown), hide the progress bar and show the loading fragment. While showing the loading fragment start 3 async network calls and wait for their completion; when the third call is completed start the methods to elaborate the data and, when both done, start the next activity.
It seems to all works just fine, but debugging I've noticed the flow (basically network calls start/wait/onCompletion) is not at all like what I've described above. There's something to fix in the ViewModel, I guess, but I can't figure out what

PositionalDataSource Not showing List

I want to fetch 20 Job items from our backend server. The api I need for fetching is position based. That's why I use PositionalDataSource
I use Rxjava for streamData and LiveData to connect my data to UI. I need to fetch 20 item per scrolling. For that reason, I use PositionalDataSource for retrieving. In Fact the Api generated by backend developer is Position based. But I don't get any value shown in my recyclerView.
JobService.kt
#GET(Constants.API_JOB_LIST)
fun getJobPost(
#Query("offset") numberOfItemPerRequest: Int
): Observable<Response<JobResponse>>
RetrofitBuilder.kt
fun retrofit(baseUrl: String): Retrofit = Retrofit.Builder()
.client(getOkHttpClient())
.baseUrl(baseUrl)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
private fun getOkHttpClient(): OkHttpClient {
return OkHttpClient().newBuilder()
.connectTimeout(3, TimeUnit.SECONDS)
.readTimeout(3, TimeUnit.SECONDS)
.writeTimeout(3, TimeUnit.SECONDS)
.addInterceptor(headersInterceptor)
.addInterceptor(loggingInterceptor)
.build()
}
ApiUtils.kt
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
val headersInterceptor = Interceptor {
val request = it.request()
it.proceed(
request.newBuilder()
.addHeader("Accept", "application/json")
.addHeader("Content-Type", "application/json")
.addHeader("api_key", "sr8fyw8hcow8efhisncsef0wefw9ef0swdcsopd")
.build()
)
}
ApiFactory.kt
val jobListApi: JobService = RetrofitBuilder
.retrofit(Constants.BASE_URL)
.create(JobService::class.java)
JobResponse.kt
data class JobResponse(
#field:SerializedName("msg") val msg: String? = null,
#field:SerializedName("data") val data: List<Job?>? = null,
#field:SerializedName("status") val status: Int? = null
)
JobDataSource.kt
class JobDataSource(
private val jobService: JobService,
private val compositeDisposable: CompositeDisposable
): PositionalDataSource<Job>() {
companion object {
private const val POSITION_OF_JOB_POST = 0
}
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Job>) {
val initialJobList = jobService.getJobPost(POSITION_OF_JOB_POST)
compositeDisposable += initialJobList
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
if (it.isSuccessful) {
val jobList = it?.body()?.data!!
callback.onResult(
jobList,
computeInitialLoadPosition(params, jobList.size),
jobList.size
)
} else {
// ... statusCode = it.code()
}
}, {
if(it is IOException) {
// ... No Internet
} else {
// ... Converter Exception(Json Parsing error)
}
})
}
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<Job>) {
val initialJobList = jobService.getJobPost(POSITION_OF_JOB_POST)
compositeDisposable += initialJobList
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
if (it.isSuccessful) {
callback.onResult(it?.body()?.data!!)
} else {
// ... statusCode = it.code()
}
}, {
if(it is IOException) {
// ... No Internet
} else {
// ... Converter Exception(Json Parsing error)
}
})
}
}
JobDataSourceFactory.kt
class JobDataSourceFactory(
private val jobService: JobService,
private val compositeDisposable: CompositeDisposable
): DataSource.Factory<Int, Job>() {
override fun create() = JobDataSource(jobService, compositeDisposable)
}
JobDataProvider.kt
class JobDataProvider(
private val jobService: JobService,
private val compositeDisposable: CompositeDisposable
) {
companion object {
private const val PAGE_SIZE = 20
private const val PREFETCH_DISTANCE = 20
}
fun getJobs(): Observable<PagedList<Job>> {
val config = PagedList.Config.Builder()
.setPageSize(PAGE_SIZE)
.setInitialLoadSizeHint(PAGE_SIZE)
.setPrefetchDistance(PREFETCH_DISTANCE)
.setEnablePlaceholders(true)
.build()
return RxPagedListBuilder(
JobDataSourceFactory(jobService, compositeDisposable),
config
).buildObservable()
}
}
JobBoardViewModel.kt
class JobBoardViewModel : BaseViewModel() {
var jobList: LiveData<PagedList<Job>> = MutableLiveData()
fun getJobList() {
val observableList: Observable<PagedList<Job>> = JobDataProvider(
ApiFactory.jobListApi,
compositeDisposable
).getJobs()
jobList = LiveDataReactiveStreams.fromPublisher(observableList.toFlowable(BackpressureStrategy.BUFFER))
}
}
JobFragment.kt
viewModel.jobList.observe(viewLifecycleOwner, Observer {
jobAdapter.submitList(it)
})

Retrofit LiveDataCallAdapter doesn't call function adapt (call)

Trying to solve this problem about 4 days, help, please.
I'm creating an app with rest API (retrofit), try to implement LiveDataCallAdapter from Google samples
https://github.com/googlesamples/android-architecture-components/tree/master/GithubBrowserSample, but retrofit doesn't call adapter method adapt for getting a response from the server.
I'm edited only NetworkBoundResourse (for working without DB)
Trying to put breakpoints, after I start repo (login), LiveDataCallAdapter fun adapt (where call.enequeue don't want start) debugging don't call
Here is my piece of code, thx
Providing my service instance
#Singleton
#Provides
fun provideRetrofit(): BrizSmartTVService {
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(LiveDataCallAdapterFactory())
.build()
.create(BrizSmartTVService::class.java)
}
There is my LiveDataCallAdapterFactory and LiveDataCallAdapter
class LiveDataCallAdapterFactory : Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
if (getRawType(returnType) != LiveData::class.java) {
return null
}
val observableType = getParameterUpperBound(0, returnType as ParameterizedType)
val rawObservableType = 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 = getParameterUpperBound(0, observableType)
return LiveDataCallAdapter<Any>(bodyType)
}
}
class LiveDataCallAdapter<R>(private val responseType: Type) :
CallAdapter<R, LiveData<ApiResponse<R>>> {
override fun responseType() = responseType
override fun adapt(call: Call<R>): LiveData<ApiResponse<R>> {
return object : LiveData<ApiResponse<R>>() {
private var started = AtomicBoolean(false)
override fun onActive() {
super.onActive()
if (started.compareAndSet(false, true)) {
Log.d("TAG", ": onActive Started ");
call.enqueue(object : Callback<R> {
override fun onResponse(call: Call<R>, response: Response<R>) {
Log.d("TAG", ": $response");
postValue(ApiResponse.create(response))
}
override fun onFailure(call: Call<R>, throwable: Throwable) {
Log.d("TAG", ": ${throwable.localizedMessage}");
postValue(ApiResponse.create(throwable))
}
})
}
}
}
}
}
There is my NetworkBoundResourse (work only with Network)
abstract class NetworkBoundResource<RequestType> {
private val result = MediatorLiveData<Resource<RequestType>>()
init {
setValue(Resource.loading(null))
fetchFromNetwork()
}
#MainThread
private fun setValue(newValue: Resource<RequestType>) {
if (result.value != newValue) {
result.value = newValue
}
}
private fun fetchFromNetwork() {
val apiResponse = createCall()
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
when (response) {
is ApiSuccessResponse -> {
setValue(Resource.success(processResponse(response)))
}
is ApiErrorResponse -> {
onFetchFailed()
setValue(Resource.error(response.errorMessage, null))
}
}
}
}
protected fun onFetchFailed() {
}
fun asLiveData() = result as LiveData<Resource<RequestType>>
#WorkerThread
protected open fun processResponse(response: ApiSuccessResponse<RequestType>) = response.body
#MainThread
protected abstract fun createCall(): LiveData<ApiResponse<RequestType>>
}
My Repo class
#Singleton
class AuthApiRepo #Inject constructor(
val apiService: BrizSmartTVService
) {
fun authLoginPass(login: String, password: String): LiveData<Resource<AuthResponse>> {
return object : NetworkBoundResource<AuthResponse>() {
override fun createCall(): LiveData<ApiResponse<AuthResponse>> {
val authLogPassBody = AuthLogPassBody(login,password,"password")
Log.d("TAG", ": $authLogPassBody");
return apiService.authLoginPass(authLogPassBody)
}
}.asLiveData()
}
}
And my AuthResponse Class
class AuthResponse {
#SerializedName("token_type")
var tokenType: String? = null
#SerializedName("access_token")
var accessToken: String? = null
#SerializedName("refresh_token")
var refreshToken: String? = null
#SerializedName("user_id")
var userId: String? = null
#SerializedName("expires_in")
var expiresIn: Long = 0
#SerializedName("portal_url")
var portalUrl: String? = null
}
My ViewModel class from where i start calling
class AuthViewModel #Inject constructor(private val authApiRepo: AuthApiRepo) : ViewModel() {
private var _isSigned = MutableLiveData<Boolean>()
val isSigned: LiveData<Boolean>
get() = _isSigned
fun signIn(login: String, password: String) {
authApiRepo.authLoginPass(login, password)
val authRespons = authApiRepo.authLoginPass(login, password)
Log.d("TAG", ": " + authRespons.value.toString());
//here will by always data null and status LOADING
}
override fun onCleared() {
super.onCleared()
}
}
So guys, finaly i found a solution. It's very simple for the peaple experienced in MVVM (live data) subject , but im beginer in MVVM and my brain exploded while I came to this.
So , the problem was I subscribed to Repo livedata from ViewModel , not from View (Fragment in my case). After i locked the chain of livedata observers View - ViewModel - Repo - Service - everything worked. Thx to all

Categories

Resources