PositionalDataSource Not showing List - android

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)
})

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>()
}

I added second response in my ApiService and I get null from second retrofit response

## ApiService ##
interface ApiService {
#GET("recipes/random")
suspend fun getRecipes(
#Header("x-api-key") apiKey: String,
#Query("number") number: Int
): Response<Recipes>
#GET("recipes/complexSearch")
suspend fun getMostPopularRecipe(
#Header("x-api-key") apiKey: String,
#Query("number") number: Int,
#Query("sort") sort: String
): Response<Recipes>
}
## Repository ##
class MainRepository (private val apiHelper: ApiHelper) {
suspend fun getRecipes() = apiHelper.getRecipes()
suspend fun getMostPopularRecipe() = apiHelper.getMostPopularRecipe()
}
## AppModule ##
const val BASE_URL = "https://api.spoonacular.com/"
val appModule = module {
factory { provideOkHttpClient() }
factory { provideRetrofit(get(), BASE_URL) }
factory{ get<Retrofit>().create(ApiService::class.java) }
//factory { provideApiService(get()) }
factory { provideNetworkHelper(androidContext()) }
factory<ApiHelper> {
return#factory ApiHelperImpl(get())
}
}
private fun provideNetworkHelper(context: Context) = NetworkHelper(context)
private fun provideOkHttpClient() = if (BuildConfig.DEBUG) {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
} else OkHttpClient
.Builder()
.build()
private fun provideRetrofit(
okHttpClient: OkHttpClient,
BASE_URL: String
): Retrofit =
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.client(okHttpClient)
.build()
## ViewModel ##
class MostPopularRecipeViewModel(
private val mainRepository: MainRepository,
private val networkHelper: NetworkHelper
) : ViewModel() {
private val _recipes = MutableLiveData<Resource<Recipes>>()
val recipes: LiveData<Resource<Recipes>>
get() = _recipes
init {
fetchRecipes()
}
private fun fetchRecipes() {
viewModelScope.launch {
if (networkHelper.isNetworkConnected()) {
mainRepository.getMostPopularRecipe().let {
if (it.isSuccessful) {
_recipes.postValue(Resource.success(it.body()))
Log.d("test", it.body().toString())
} else _recipes.postValue(Resource.error(it.errorBody().toString(), null))
}
} else _recipes.postValue(Resource.error("No internet connection", null))
}
}
}
How can i make the second response not null? Application works, first response not null. I checked response with postman and it isn't null. I am new with koin and retrofit, and i have no idea. I have thought problem from my AppModule? but i don't know, how to correct this................................

Recursive synthetic property accessor, Can't get the size of list in PagingDataAdapter casue java.lang.StackOverflowError

In this app, I tried to use Paging3, Retrofit, and RXJava to get a list of popular movies, this is the link of API, the problem is in MoviesDataAdapter class when I didn't override getItemCount fun the recyclerView didn't show anything but the result showing in Log when I return item count, the AS shows warning notify on this method
java.lang.StackOverflowError: stack size 8192KB
at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
2022-06-10 22:48:58.080 10543-10543/com.mml.moviemvvm E/AndroidRuntime: at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
let's start with model classes Movie
data class Movie(
val id: Int,
#SerializedName("poster_path")
val posterPath: String,
#SerializedName("release_date")
val releaseDate: String,
val title: String
)
and MovieResponse
data class MovieResponse(
val page: Int,
#SerializedName("results")
val movieList: List<Movie>,
#SerializedName("total_pages")
val totalPages: Int,
#SerializedName("total_results")
val totalResults: Int
)
enum Status & NetworkState class
enum class Status {
LOADING,
SUCCESS,
FAILED
}
class NetworkState(val status: Status, val msg: String) {
companion object {
val SUCCEED: NetworkState = NetworkState(Status.SUCCESS, "Success")
val LOADING: NetworkState = NetworkState(Status.LOADING, "Running")
val ERROR: NetworkState = NetworkState(Status.FAILED, "Something went wrong")
val ENDOFLIST = NetworkState(Status.FAILED, "You have reached the end")
}
}
TheMovieDbApi interface
interface TheMovieDbApi {
#GET("movie/popular")
fun getPopularMovies(#Query("page") page: Int): Single<MovieResponse>
#GET("movie/{movie_id}")
fun getMovieDetails(#Path("movie_id") id: Int): Single<MovieDetails>
}
TheMovieClient
const val API_KEY = "some key"
const val BASE_URL = "https://api.themoviedb.org/3/"
const val POSTER_BASE_URL = "https://image.tmdb.org/t/p/w342"
const val FIRST_PAGE = 1
const val POST_PER_PAGE = 20
object TheMovieDBClient {
fun getClient(): TheMovieDbApi {
val requestInterceptor = Interceptor { chain ->
// Interceptor take only one argument which is a lambda function so parenthesis can be omitted
val url = chain.request()
.url()
.newBuilder()
.addQueryParameter("api_key", API_KEY)
.build()
val request = chain.request()
.newBuilder()
.url(url)
.build()
return#Interceptor chain.proceed(request) //explicitly return a value from whit # annotation. lambda always returns the value of the last expression implicitly
}
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(requestInterceptor)
.connectTimeout(60, TimeUnit.SECONDS)
.build()
return Retrofit.Builder()
.client(okHttpClient)
.baseUrl(BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(TheMovieDbApi::class.java)
}
}
MoviesPagingSource class
class MoviesPagingSource(
private val apiService: TheMovieDbApi,
) : PagingSource<Int, Movie>() {
override fun getRefreshKey(state: PagingState<Int, Movie>): Int? {
TODO("Not yet implemented")
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movie> {
val position = params.key ?: FIRST_PAGE
return try {
var movieResponse = MovieResponse(3,
listOf(),1,1)
apiService.getPopularMovies(position)
.subscribeOn(Schedulers.io())
.subscribe(
{
if(it != null){
movieResponse = it
Log.e(TAG, "load: $it" )
}
}, { throwable ->
Log.e(TAG, "load: ${throwable.cause?.message}")
}
)
LoadResult.Page(
data = movieResponse.movieList,
prevKey = if (position == FIRST_PAGE) null else
position - 1,
nextKey = if (movieResponse.movieList.isEmpty()) null else position + 1
)
} catch (ex: IOException) {
LoadResult.Error(ex)
} catch (ex: HttpException) {
LoadResult.Error(ex)
}
}
}
MoviesLoadStateAdapter
class MoviesLoadStateAdapter(private val retry: () -> Unit) :
LoadStateAdapter<MoviesLoadStateAdapter.LoadStateViewHolder>() {
inner class LoadStateViewHolder(private val binding: MoviesLoadStateFooterBinding) :
RecyclerView.ViewHolder(binding.root) {
init {
binding.buttonRetry.setOnClickListener {
retry.invoke()
}
}
fun bind(loadState: LoadState) {
binding.apply {
progressBar.isVisible = loadState is LoadState.Loading
buttonRetry.isVisible = loadState !is LoadState.Loading
textViewError.isVisible = loadState !is LoadState.Loading
}
}
}
override fun onBindViewHolder(holder: LoadStateViewHolder, loadState: LoadState) {
holder.bind(loadState)
}
override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): LoadStateViewHolder {
val binding: MoviesLoadStateFooterBinding = MoviesLoadStateFooterBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return LoadStateViewHolder(binding)
}
}
MoviesReposory class
class MoviesRepository(private val api: TheMovieDbApi) {
fun getMoviesResults() =
Pager(
config = PagingConfig(
pageSize = 20,
maxSize = 100,
enablePlaceholders = false,
),
pagingSourceFactory = { MoviesPagingSource(api) }
).liveData
}
MovieDataAdapter
class MoviesDataAdapter(private val listener: MainActivity) :
PagingDataAdapter<Movie, MoviesDataAdapter.MovieItemViewHolder>(MOVIE_COMPARATOR) {
override fun onBindViewHolder(holder: MovieItemViewHolder, position: Int) {
val movie = getItem(position)
holder.bind(movie)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieItemViewHolder {
val binding = MovieListItemBinding.inflate(
LayoutInflater.from(parent.context),
parent, false
)
return MovieItemViewHolder(binding)
}
override fun getItemCount(): Int {
return itemCount
}
companion object {
private val MOVIE_COMPARATOR = object : DiffUtil.ItemCallback<Movie>() {
override fun areItemsTheSame(oldItem: Movie, newItem: Movie) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Movie, newItem: Movie) =
oldItem == newItem
}
}
interface OnClickListener{
fun onItemClick(movie: Movie?)
}
inner class MovieItemViewHolder(private val binding: MovieListItemBinding) :
RecyclerView.ViewHolder(binding.root) {
init {
binding.root.setOnClickListener {
val position = bindingAdapterPosition
if (position != RecyclerView.NO_POSITION) {
val item = getItem(position)
if (item != null) {
listener.onItemClick(item)
}
}
}
}
fun bind(movie: Movie?) {
binding.apply {
cvMovieTitle.text = movie?.title
cvMovieReleaseDate.text = movie?.releaseDate
val moviePosterURL = POSTER_BASE_URL + movie?.posterPath
Glide.with(cardView.context)
.load(moviePosterURL)
.into(cvIvMoviePoster)
}
}
}
}
when I run the app without overriding getItemCount fun it's showing no result but it printed in the log

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

Doing multiple RxJava calls and using result for next call

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()
}
}

Categories

Resources