How to use Coroutines with Retrofit2? - api

I am fetching data from an api to display in a RecycerView using Retrofit2 and Kotlin Coroutines. I have just started learning Retrofit and Coroutines and at the moment the data is not displaying and I'm not sure how to solve it! I think the issue may be with the Coroutines code. Please can someone give me some help?
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var recyclerView: RecyclerView = findViewById(R.id.rockets_list)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = RecyclerAdapter(List<RocketData>())
CoroutineScope(IO).launch {
val response = ApiInterface.getApi().getRockets()
Log.i("code",response.toString())
withContext(Dispatchers.Main) {
try {
if (response.isSuccessful) {
recyclerView.adapter
} else {
Toast.makeText(this#MainActivity, "Error ${response.code()}", Toast.LENGTH_SHORT).show()
}
} catch (e: HttpException) {
Toast.makeText(this#MainActivity, "Exception ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
}
}
interface ApiInterface {
#GET("rockets")
suspend fun getRockets(): Response<List<RocketData>>
companion object {
fun getApi(): ApiInterface = Retrofit.Builder()
.baseUrl("https://api.spacexdata.com/v3/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiInterface::class.java)
}
}

What are you doing after response.isSuccessful check??
try setting response in Adapter then notifyDataSetChanged
recyclerView.adapter.items = response
recyclerView.adapter.notifyDataSetChanged()

your Retrofit and Coroutine implementation works fine,
you just didn't update your adapter list after successful response check
if (response.isSuccessful) {
recyclerView.adapter.list = response.body
recyclerView.adapter.notifyDataSetChanged()
}
also you can't display Toast from within a Coroutine, wrap it inside runOnUiThread{}

Related

Can't log any data from API call

I'm pretty new to kotlin, and I feel pretty much overwhelmed by it. I'd like to ask - how I can display any data from MutableLiveData? I've tried to Log it, but it doesn't seem to work. I've already added the internet permission to the manifest. Here's the code:
ApiServices
interface ApiServices {
#GET("/fixer/latest/")
fun getRatesData(
#Query("base") base: String,
#Query("apikey") apikey: String
): Call<CurrencyModel>
companion object {
private const val url = "https://api.apilayer.com/"
var apiServices: ApiServices? = null
fun getInstance(): ApiServices {
if (apiServices == null) {
val retrofit = Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build()
apiServices = retrofit.create(ApiServices::class.java)
}
return apiServices!!
}
}
}
Repository
class CurrencyRepository constructor(private val apiServices: ApiServices) {
fun getLatestRates() = apiServices.getRatesData("EUR", "API_KEY");
}
ViewModel
class CurrencyViewModel constructor(private val currencyRepository: CurrencyRepository) :
ViewModel() {
val currencyRatesList = MutableLiveData<CurrencyModel>()
val errorMessage = MutableLiveData<String>()
fun getLatestRates() {
val response = currencyRepository.getLatestRates();
response.enqueue(object : retrofit2.Callback<CurrencyModel> {
override fun onResponse(
call: retrofit2.Call<CurrencyModel>,
response: Response<CurrencyModel>
) {
currencyRatesList.postValue(response.body())
}
override fun onFailure(call: retrofit2.Call<CurrencyModel>, t: Throwable) {
errorMessage.postValue(t.message)
}
})
}
}
FactoryViewModel
class CurrencyViewModelFactory constructor(private val repository: CurrencyRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(CurrencyViewModel::class.java)) {
CurrencyViewModel(this.repository) as T
}else{
throw IllegalArgumentException("Couldn't found ViewModel")
}
}
}
MainActivity
class MainActivity : AppCompatActivity() {
private val retrofitService = ApiServices.getInstance()
lateinit var viewModel: CurrencyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this, CurrencyViewModelFactory(CurrencyRepository(retrofitService)))
.get(CurrencyViewModel::class.java)
viewModel.currencyRatesList.observe(this, Observer {
Log.d(TAG, "onCreate: $it")
})
viewModel.errorMessage.observe(this, Observer {
viewModel.getLatestRates()
})
}
}
You never call viewModel.getLatestRates() in your onCreate() to fetch an initial value for your LiveData, so it never emits anything to observe. The only place you have called it is inside your error listener, which won't be called until a fetch returns an error.
Side note, I recommend naming the function "fetchLatestRates()". By convention, "get" implies that the function returns what it's getting immediately rather than passing it to a LiveData later when its ready.
And a tip. Instead of this:
class MainActivity : AppCompatActivity() {
lateinit var viewModel: CurrencyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
viewModel = ViewModelProvider(this, CurrencyViewModelFactory(CurrencyRepository(retrofitService)))
.get(CurrencyViewModel::class.java)
//...
}
}
You can do this for the same result:
class MainActivity : AppCompatActivity() {
val viewModel: CurrencyViewModel by viewModels(CurrencyViewModelFactory(CurrencyRepository(retrofitService)))
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
}
}

problem with use Mvvm and Retrofit and Rxjava live data in kotlin

I'm new in kotlin and I'm trying to use retrofit with Rxjava and live data in MVVM architecture.
I config retrofit, and also use observable and subscribe in ViewModel to make observable variable to use in activity binding layout.
I have a button in my view and when I click on it, the request method gets to start and subscription write a log of its own data. but my variable gets null at first and after seconds, when retrofit completed its task, my variable gets data but log value doesn't update.
this is my retrofit initialize class
class ApiService {
private val INSTANCE =
Retrofit.Builder()
.baseUrl("http://www.janbarar.ir/App/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(IRetrofitMethods::class.java)
private fun <T> callBack(iDataTransfer: IDataTransfer<T>) =
object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val data = response.body()
if (data != null)
iDataTransfer.onSuccess(data)
else
try {
throw Exception("data is empty")
} catch (ex: Exception) {
iDataTransfer.onError(ex)
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
iDataTransfer.onError(t)
}
}
fun getCategories(iDataTransfer: IDataTransfer<List<Category>>) =
INSTANCE.getCategories().enqueue(callBack(iDataTransfer))
this is an interface for retrofit
#GET("GetCategories")
fun getCategories(): Call<List<Category>>
this is my model class. I think the problem is here. because the observable send null data before retrofit finish its work.
fun getCategories(): Observable<ArrayList<Category>> {
val result = arrayListOf<Category>()
api.getCategories(object : IDataTransfer<List<Category>> {
override fun onSuccess(data: List<Category>) {
result.addAll(data)
}
override fun onError(throwable: Throwable) {
Log.e("Model", throwable.message!!)
}
})
return Observable.just(result)
}
and this is also my ViewModel class
class ProductViewModel(private val model: ProductModel) : ViewModel() {
var isLoading = ObservableField(false)
var categoryList = MutableLiveData<ArrayList<Category>>()
private var compositeDisposable = CompositeDisposable()
fun getCategories() {
isLoading.set(true)
compositeDisposable +=
model.getCategories()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
categoryList.value = it
}, {
Log.e("ViewModel", it.message.toString())
})
isLoading.set(false)
}
finally, it's my activity
lateinit var binding: ActivityMainBinding
private val vm: ProductViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.vm = vm
vm.categoryList.observe(this, Observer {
if (it != null)
Log.e("activity", it.toString())
})
}
As ExpensiveBelly mentioned in a comment, Retrofit provides a call adapter for RxJava, so you can let your API return Observable<List<Category>> directly. To do this, you will need to add the RxJava call adapter dependency to your app module's build.gradle:
implementation 'com.squareup.retrofit2:adapter-rxjava2:(version)'
Add the call adapter factory when constructing your Retrofit instance:
private val INSTANCE =
Retrofit.Builder()
.baseUrl("http://www.janbarar.ir/App/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // add this
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(IRetrofitMethods::class.java)
And then just let your service return an Observable directly:
#GET("GetCategories")
fun getCategories(): Observable<List<Category>>
If ApiService needs to do some handling before the rest of the app gets the response, you can use RxJava operators like map.
It would be illustrative to see why your code doesn't work and how to fix it. When you call api.getCategories(someCallback), one of your callback methods will be called at some point in the future. In the meantime, the model.getCategories() method will return immediately.
When you subscribe to the returned Observable, it emits the result variable, which is currently an empty list. result will eventually have some data in it, but your code will not be informed of this at all.
What you really want to do is emit the list of categories when it becomes available. The standard way to get an Observable from a callback API is with Observable.create:
fun getCategories(): Observable<ArrayList<Category>> {
return Observable.create { emitter ->
api.getCategories(object : IDataTransfer<List<Category>> {
override fun onSuccess(data: List<Category>) {
emitter.onNext(data)
emitter.onComplete()
}
override fun onError(throwable: Throwable) {
emitter.onError(throwable)
}
})
}
}
Of course, it's better to just use RxJava2CallAdapterFactory if possible, since this work has already been done there.

Coroutine never calls API after job either cancelled or pending

How do I make calls when I click loan1 button happen after getting a JobCancellationException on other calls ?
class MainRepository{
//called many times
suspend fun getLoanOptions(): Resource<LoanOptionsResponse> {
return try {
val response = apiService.getLoanOptions("id")
responseHandler.handleSuccess(response)
} catch (e: Exception) {
responseHandler.handleException(e)
}
}
}
class MainViewModel : ViewModel(), CoroutineScope {
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
Timber.e("$throwable")
}
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + SupervisorJob() + exceptionHandler
private val mainRepo: MainRepository by lazy { MainRepository() }
//extra calls to this fails
fun getLoanOptions(): LiveData<Resource<LoanOptionsResponse>> {
return liveData(coroutineContext) {
val data = mainRepo.getLoanOptions()
emit(Resource.loading(null))
emit(data)
}
}
override fun onCleared() {
super.onCleared()
coroutineContext.cancel()
}
}
//in mainactivity I call it
class MainActivity : BaseActivity() {
val vm: MainViewModel by lazy { ViewModelProvider(this).get(MainViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loan1.setOnClickListener {
// if any previous api has 403 this one does not work any more ?
vm.getLoanOptions().observe(this, Observer {
//data
}
}
loan2.setOnClickListener {
}
}
}
I clicked on button -> Api is called -> got success response
Again I clicked on -> Api is called -> got success response
I get 403 in other API call
I clicked on button -> Api is not called
No other Api calls gets called in this Activity :(
I get this after few minutes
kotlinx.coroutines.JobCancellationException: Job was cancelled; job=StandaloneCoroutine

NetworkOnMainThreadException with Retrofit and Rxandroid in kotlin

This question has asked many times but none of the solutions is working. I don't know what's going on with code. Always shows android.os.NetworkOnMainThreadException
Below code is my ViewModel class
class MainActicityViewModel(val context: MainActivity) : BaseObservable() {
val layoutManagerAppointment = LinearLayoutManager(context)
private val appClient = Appclient().getClient(context)
private val apiInterface = appClient.create(ApiInterface::class.java)
var rcyAdapter: RcyAdapter? = null
init {
getList()
}
fun getList() {
val observable = apiInterface.getUsers()
observable.subscribeOn(Schedulers.io()) // i have also registerd for Schedulers.newThread()
observable.observeOn(AndroidSchedulers.mainThread())
observable.subscribeWith(object : DisposableObserver<ArrayList<Data>>() {
override fun onComplete() {
Toast.makeText(context, "onComplete", Toast.LENGTH_SHORT).show()
}
override fun onNext(t: ArrayList<Data>) {
Log.e("SIZE", "" + t.size)
Toast.makeText(context, "onNext", Toast.LENGTH_SHORT).show()
}
override fun onError(e: Throwable) {
Toast.makeText(context, "onError", Toast.LENGTH_SHORT).show()
Log.e("TAG ON ERROR", "" + e)
}
})
}
}
Below code is Retrofit Client class
class Appclient {
private var retrofit: Retrofit? = null
private val baseUrl = "https://jsonplaceholder.typicode.com"
fun getClient(context: Context): Retrofit {
//Here a logging interceptor is created
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder()
.addInterceptor(interceptor).build()
retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(client)
.build()
return (retrofit)!!
}}
And my retrofit interface
interface ApiInterface {
#GET("/users")
fun getUsers(): Observable<ArrayList<Data>>}
Here is the MainActivity class
class MainActivity : AppCompatActivity() {
lateinit var vModel: MainActicityViewModel
var mainBinding: ActivityMainBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = DataBindingUtil.setContentView(this#MainActivity, R.layout.activity_main)
vModel = MainActicityViewModel(this#MainActivity)
mainBinding!!.viewModel = vModel
}}
Try this...
fun getList() {
val observable:Observable<ArrayList<Data>> = apiInterface.getUsers()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableObserver<ArrayList<Data>>() {
override fun onComplete() {
Toast.makeText(context, "onComplete", Toast.LENGTH_SHORT).show()
}
override fun onNext(t: ArrayList<Data>) {
Log.e("SIZE", "" + t.size)
Toast.makeText(context, "onNext", Toast.LENGTH_SHORT).show()
}
override fun onError(e: Throwable) {
Toast.makeText(context, "onError", Toast.LENGTH_SHORT).show()
Log.e("TAG ON ERROR", "" + e)
}
})
}
Observable needs to be chain calls when you do RxJava operations because Observable is not the builder pattern.

subscribe function is not working

I am trying the basics of RxJava2.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_vogella)
setSupportActionBar(toolbar)
val todoObserverable= createObservable();
try {
todoObserverable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe ({ t-> Log.e(TAG,t.title)}, {e-> Log.e(TAG,e.localizedMessage)})
}catch (e:Exception){
e.printStackTrace()
}
}
get observable function:
fun createObservable():Observable<Book>{
val bookObservable: Observable<Book> = Observable.create { object :ObservableOnSubscribe<Book>{
override fun subscribe(emitter: ObservableEmitter<Book>) {
Log.e(TAG,"anc")
try {
val bookArrayList:ArrayList<Book> = ArrayList()
val bookOne= Book("XYZ")
val bookTwo= Book("ANC")
val bookThree= Book("3ewrXYZ")
val bookFour= Book("XwerweYZ")
bookArrayList.add(bookOne)
bookArrayList.add(bookTwo)
bookArrayList.add(bookThree)
bookArrayList.add(bookFour)
for (todo in bookArrayList){
emitter.onNext(todo)
Log.e(TAG,"on next")
}
emitter.onComplete()
}catch (e:Exception){
e.printStackTrace()
}
}
}
}
return bookObservable;
}
But I am unable to print the title of the book. It is not giving me any kind of error or exception.
I tried to debug the createObservable() but curser is not going inside the subscribe function.
Any hint will be helpful.
Observable.create { object :ObservableOnSubscribe<Book>{ - This essentially creates a ObservableOnSubscribe within a ObservableOnSubscribe. The object declaration is redundant or you can remove the lambda definition. (Observable.create(object : ETC))