I was making a call using Retrofit before I tried to use Paging 3 library and I was getting code 200 and all working good.Then, I tried to implement Paging 3 libray and using Retrofit with it and I don't know why, I'm getting error 401. I tried some Paging 3 sample and all of this samples don't use headers in the API call, is there any problem to add headers in API call using Paging 3? I'm not sure if there is a problem with the use of headers or I'm just doing something wrong implementing Paging 3 library.
My code:
Service:
interface APICalls{
#GET(MYAPIURL)
suspend fun getData(
#Header("Auth") auth : String,
#Query("pageSize") pageSize:Int
):Response<ResponseData>
}
Models:
data class ResponseData(
#SerializedName("listData") val listData:MutableList<DataAPI>,
#SerializedName("pageSize") val pageSize : Int
):Serializable
data class DataAPI(
#SerializedName("id") val id:Int,
#SerializedName("data")val data: String
): Serializable
Result wrapper:
class Result<out T:Any>{
data class Success<T:Any>(val value: T): Result<T>()
data class Failure(val message:String, val errorCode:Int?):Result<Nothing>()
}
PagingSource:
val responseData = mutableListOf<DataAPI>()
class DataAPIPagingSource(private val token:String,private val apiCalls:APICalls) : PagingSource<Int,DataAPI>{
override fun getRefreshKey(...):Int?{
return null
}
override suspend fun load(params : LoadParams<Int>):LoadResult<Int,DataAPI>{
return try{
val currentPage = params.key ?: 1
val response = apiCalls.getData(token)
response.body()?.let{
Result.Success(it)
}?: run{
Result.Failure(response.message(),response.code())
}
val data = response.body()?.listData ?: emptyList()
responseData.addAll(data)
LoadResult.Page(responseData,if(currentPage ==1) null else -1),currentPage.plus(1)
}catch(e:Exception){
LoadResult.Error(e)
}
}
}
ViewModel:
class DataViewModel(private val apiCalls:APICalls): ViewModel {
//I get this token in shared preference
val token = .....
val mydata = getDataList()
.map{pagingData -> pagingData.map{DataModel.DataItem(it)}}
private fun getDataList(){
return Pager(PagingConfig(25)){
DataAPIPagingSource(token,apiCalls)
}.flow.cachedIn(viewModelScope)
}
}
sealed class DataModel{
data class DataItem(val dataitem: DataAPI) : DataModel()
}
private val DataModel.DataItem.identificator : Int
get() = this.dataItem.id
Fragment:
class MyFragment : Fragment(){
private val myAdapter : DataAdapter by lazy{
DataAdapter()
}
private val viewModelFactory : ViewModelFactory by inject()
private val dataViewModel : DataViewModel by navGraphViewModels(R.id.nav_graph){viewModelFactory}
override fun onViewCreated(...){
super.onViewCreated(...)
binding.myRecyclerView.apply{
adapter = myAdapter
layoutManager = StaggeredGridLayoutManager(1,StaggeredGridLayoutManager.VERTICAL)
setHasFixedSize(true)
}
lyfecycleScope.launch{
dataViewModel.mydata.collect{myAdapter.submitData(it)}
}
}
}
Adapter:
class DataAdapter : PagingDataAdapter<DataModel,RecyclerView.ViewHolder>(DataModelComparator){
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position:Int){
val dataModel : DataModel? = getItem(position)
dataModel.let{
when(dataModel){
is DataModel.DataItem ->{
val viewHolder = holder as DataItemViewHolder
viewHolder.binding.textview1.text = dataModel.dataitem.data
}
}
}
}
override fun getItemViewType(position:Int):Int{
return when(getItem(position)){
is DataModel.DataItem -> R.layout.item_data
null -> throw UnsupportedOperationException("Unknown view")
}
}
override fun onCreateViewHolder(...){
return when(viewType){
R.layout.item_data ->{
DataItemViewHolder(ItemDataBinding.inflate(...))
}
}
}
class DataItemViewHolder(val binding: DataItemBinding): RecyclerView.ViewHolder(binding.root)
companion object {
val DataModelComparator = object : DiffUtil.ItemCallback<DataModel>() {
override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean {
return oldItem.dataitem.id == newItem.dataitem.id
}
override fun areContentsTheSame(oldItem: DataModel, newItem: DataModel): Boolean {
return oldItem == newItem
}
}
}
}
I don't think the 401 error is related to paging 3.
You can use OkHttp Interceptor - authenticator.
Gist
Related
I have created a remote mediator which gets movies from api call and adds it to database which is then used as a source to load the data on screen.
It is pretty cliche implementation done same as google developers video of paging3 from youtube , diffrent articles etc.
#ExperimentalPagingApi
class RemoteMediator(
val moviesRetrofitClient: MoviesRetrofitClient,
private val movieDatabase: MovieDatabase
) : RemoteMediator<Int, MovieData>() {
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, MovieData>
): MediatorResult {
try {
val pageKeyData = getKeyPageData(loadType , state)
val page = when(pageKeyData){
is MediatorResult.Success -> {
Utils.debug("mediator result success = $pageKeyData")
return pageKeyData
}
else -> {
Utils.debug("mediator result failed = $pageKeyData")
pageKeyData as Int
}
}
Utils.debug("page we got = $page")
val movieResponse = moviesRetrofitClient.getNowPlayingMovies(page)
val movies = movieResponse.movies
var totalPages = movieResponse.totalPages
val endOfPaginationReached = (page == totalPages)
movieDatabase.withTransaction {
if (loadType == LoadType.REFRESH){
movieDatabase.movieDao().deleteMovie()
movieDatabase.moviePagingKeyDao().deleteAllPagingKeys()
}
val prevPage = if (page == 1) null else (page-1)
val nextPage = if (endOfPaginationReached) null else (page+1)
val keys = movies.map {
MoviePagingKeys(it.id , prevPage = prevPage , nextPage = nextPage)
}
movieDatabase.moviePagingKeyDao().addAllPagingKeys(keys)
movieDatabase.movieDao().addMovies(movies)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
}catch (e : Exception){
Utils.error("exception Error : ${e.message.toString()}")
return MediatorResult.Error(e)
}catch (ioException : IOException){
Utils.error("IO Error : ${ioException.message.toString()}")
return MediatorResult.Error(ioException)
}
}
private suspend fun getKeyPageData(loadType: LoadType, state: PagingState<Int,
MovieData>): Any {
return when(loadType){
LoadType.REFRESH -> {
Utils.debug("Refresh called")
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextPage?.minus(1) ?: 1
}
LoadType.APPEND -> {
Utils.debug("Append called")
val remoteKeys = getLastRemoteKey(state)
val nextKey = remoteKeys?.nextPage
return nextKey ?: MediatorResult.Success(endOfPaginationReached = false)
}
LoadType.PREPEND -> {
Utils.debug("Prepend Called")
val remoteKeys = getFirstRemoteKey(state)
val prevKey = remoteKeys?.prevPage ?: return MediatorResult.Success(
endOfPaginationReached = false
)
prevKey
}
}
}
private suspend fun getFirstRemoteKey(state: PagingState<Int, MovieData>):
MoviePagingKeys?{
return state.pages
.firstOrNull { it.data.isNotEmpty() }
?.data?.firstOrNull()
?.let { movie -> movieDatabase.moviePagingKeyDao().getMoviePagingKey(movie.id) }
}
private suspend fun getLastRemoteKey(state: PagingState<Int, MovieData>): MoviePagingKeys?
{
return state.pages
.lastOrNull { it.data.isNotEmpty() }
?.data?.lastOrNull()
?.let { movie -> movieDatabase.moviePagingKeyDao().getMoviePagingKey(movie.id) }
}
private suspend fun getRemoteKeyClosestToCurrentPosition(state: PagingState<Int,
MovieData>): MoviePagingKeys? {
return state.anchorPosition?.let {position ->
state.closestItemToPosition(position)?.id?.let { movieId ->
movieDatabase.moviePagingKeyDao().getMoviePagingKey(movieId)
}
}
}
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
}
This is my api response
{
"dates": {
"maximum": "2022-09-11",
"minimum": "2022-07-25"
},
"page": 1,
"results": [
{
"adult": false,
"backdrop_path": "/2RSirqZG949GuRwN38MYCIGG4Od.jpg",
"genre_ids": [
53
],
"id": 985939,
"original_language": "en",
"original_title": "Fall",
"overview": "For best friends Becky and Hunter, life is all about conquering fears and pushing limits. But after they climb 2,000 feet to the top of a remote, abandoned radio tower, they find themselves stranded with no way down. Now Becky and Hunter’s expert climbing skills will be put to the ultimate test as they desperately fight to survive the elements, a lack of supplies, and vertigo-inducing heights.",
"popularity": 9791.409,
"poster_path": "/9f5sIJEgvUpFv0ozfA6TurG4j22.jpg",
"release_date": "2022-08-11",
"title": "Fall",
"video": false,
"vote_average": 7.5,
"vote_count": 455
},...]
"total_pages": 83,
"total_results": 1645
}
The results are the movies which needs to be displayed . Since an array of movies are already fetched during the api call , I am checking if the remote mediator is success or not by comparing the page number with the total pages.
val endOfPaginationReached = (page == totalPages)
The problem is , the load method is called continously again and again even after 1st page is fetched . Hence making it call the API continously.
I understand the data which i gave might not be enough for a solution , but I do not know how to express the problem.
I want to know how is the load method called , like on what condition. Please help
This is all the classes which is being used , I am not adding the unrelated classes like Daos and ViewModels. I am sure those does not have any problems.
Repository clas with the config :-
class MovieRepository #Inject constructor(
val moviesRetrofitClient: MoviesRetrofitClient,
val movieDatabase: MovieDatabase) {
fun getMovies() = Pager(
config = PagingConfig(pageSize = Constants.PAGE_SIZE, maxSize = Constants.MAX_PAGE_COUNT),
remoteMediator = RemoteMediator(moviesRetrofitClient , movieDatabase)){
movieDatabase.movieDao().getMovies()
}.liveData
}
Retrofit client
#InstallIn(SingletonComponent::class)
#Module
class MoviesRetrofitClient #Inject constructor() {
#Singleton
#Provides
fun getInterceptor() : Interceptor{
val requestInterceptor = Interceptor{
val url = it.request()
.url
.newBuilder()
.addQueryParameter("api_key" , API_KEY)
.build()
val request = it.request()
.newBuilder()
.url(url)
.build()
return#Interceptor it.proceed(request)
}
return requestInterceptor
}
#Singleton
#Provides
fun getGsonConverterFactory() : GsonConverterFactory{
return GsonConverterFactory.create()
}
#Singleton
#Provides
fun getOkHttpClient() : OkHttpClient{
var httLog : HttpLoggingInterceptor = HttpLoggingInterceptor()
httLog.setLevel(HttpLoggingInterceptor.Level.BODY)
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(getInterceptor()).addInterceptor(httLog)
.connectTimeout(60 , TimeUnit.SECONDS)
.build()
return okHttpClient
}
#Singleton
#Provides
fun getMoviesApiServiceRx() : MoviesApiService{
var retrofit : Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(getOkHttpClient())
.addConverterFactory(getGsonConverterFactory())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
return retrofit.create(MoviesApiService::class.java)
}
#Singleton
#Provides
suspend fun getNowPlayingMovies(pageNo : Int): NowPlayingMoviesData {
return getMoviesApiServiceRx().getNowPlayingMovies(pageNo)
}
}
Paging Adapter
class MoviesAdapter() : PagingDataAdapter<MovieData,MoviesAdapter.MovieViewHolder>(COMPARATOR) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
val binding = MovieViewBinding.inflate(LayoutInflater.from(parent.context), parent , false)
return MovieViewHolder(context = parent.context , binding)
}
override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
val movie = getItem(position)
if (movie != null){
holder.bindData(movie)
}
}
inner class MovieViewHolder(private val context: Context, private val movieViewDataBinding : MovieViewBinding)
: RecyclerView.ViewHolder(movieViewDataBinding.root){
init {
movieViewDataBinding.root.setOnClickListener{
// TODO: "implement movie details screen"
Utils.toast(context , "movie Clicked")
}
}
fun bindData(movieData: MovieData){
movieViewDataBinding.movie = calculateRating(movieData)
}
//change the ratings to the multiple of 5 , so that it can be fit in the rating view.
private fun calculateRating(movieData: MovieData) : MovieData{
movieData.voteAverage = (movieData.voteAverage?.times(5))?.div(10)
return movieData
}
}
companion object {
private val COMPARATOR = object : DiffUtil.ItemCallback<MovieData>(){
override fun areItemsTheSame(oldItem: MovieData, newItem: MovieData): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: MovieData, newItem: MovieData): Boolean {
return oldItem == newItem
}
}
}
}
Loading adapter for progress circle when scrolling
class LoaderAdapter : LoadStateAdapter<LoaderAdapter.LoaderHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): LoaderHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.loader , parent , false)
return LoaderHolder(view)
}
override fun onBindViewHolder(holder: LoaderHolder, loadState: LoadState) {
holder.bind(loadState)
}
inner class LoaderHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val progress = itemView.findViewById<ProgressBar>(R.id.movieProgressBar)
fun bind(loadState: LoadState){
progress.isVisible = loadState is LoadState.Loading
}
}
}
Edit :
This is my main Activity.
class MainActivity : AppCompatActivity(), SwipeRefreshLayout.OnRefreshListener,
View.OnClickListener{
lateinit var movieViewModel : MoviesViewModel
lateinit var moviesAdapter : MoviesAdapter
lateinit var movieRecyclerView: RecyclerView
lateinit var connectivityLiveStatus: ConnectionLiveStatus
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
init()
}
private fun init(){
connectivityLiveStatus = ConnectionLiveStatus(this)
observeConnectivity()
swipeToRefresh.setOnRefreshListener(this)
movieRecyclerView = findViewById(R.id.moviesRecyclerView)
moviesAdapter = MoviesAdapter()
movieViewModel = ViewModelProvider(this)[MoviesViewModel::class.java]
movieRecyclerView.layoutManager = LinearLayoutManager(this)
movieRecyclerView.setHasFixedSize(true)
movieRecyclerView.adapter = moviesAdapter.withLoadStateHeaderAndFooter(
header = LoaderAdapter(),
footer = LoaderAdapter()
)
nowPlayingTV.setOnClickListener(this)
observeViewModel()
}
//observe connectivity change
private fun observeConnectivity(){
connectivityLiveStatus.observe(this , Observer {status ->
handleConnectivityChange(status)
})
}
//Observe the movie data change
private fun observeViewModel(){
movieViewModel.movieList.observe(this) {
moviesAdapter.submitData(lifecycle, it)
if (swipeToRefresh.isRefreshing) swipeToRefresh.isRefreshing = false
}
}
private fun handleConnectivityChange(status : Boolean){
networkConnectivityStatusTv.visibility = if (status) View.INVISIBLE else View.VISIBLE
nowPlayingTV.visibility = if (status) View.VISIBLE else View.GONE
moviesAdapter.retry()
//change the status bar color according to network status.
val window = window
window.statusBarColor = if (status) applicationContext.resources.getColor(R.color.app_background_color) else applicationContext.resources.getColor(
R.color.network_connectivity_alert_color
)
}
//refresh when swipe
override fun onRefresh() {
moviesAdapter.refresh()
}
override fun onClick(p0: View?) {
when(p0?.id) {
R.id.nowPlayingTV -> {
movieRecyclerView.smoothScrollToPosition(0)
}
}
}
}
And this line of code , which I used to display loading progress while scrolling ( using the LoadAdapter) .
When I remove these lines , The entire paging stops working , No api gets called .
What exactly does this line of code do . is there any other way for this ?
Could this be calling the load from remote mediator again and again ?
You are refreshing the list everytime it's visited:
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
use this one:
return InitializeAction.SKIP_INITIAL_REFRESH
you can read further here: https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator#initialize()
In my project I want to use paging 3 .
before adding paging into my project , I could get the data from server and show into my RecyclerView
but after adding paging I faced with this issue :
in my Paging Source class :
class RepoPagingSource #Inject constructor(
private val repository: ApiRepository,
val context: Context) : PagingSource<Int, RepositoryResponse>() {
private lateinit var sharedPref: SharedPref
private lateinit var data : MutableList<RepositoryResponse>
private lateinit var responseCode : String
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, RepositoryResponse> {
sharedPref = SharedPref(context)
val responseData = mutableListOf<RepositoryResponse>()
return try {
val currentPage = params.key ?: 1
val response = repository
.getRepositories("bearer ${sharedPref.accessToken}", currentPage)
.applyIoScheduler()
.subscribe { response ->
responseCode=response.code().toString()
data = response.body()!!
Log.d("RepoPagingSource",responseCode)
Log.d("RepoPagingSource",data.size.toString())
Log.d("RepoPagingSource",data.toString())
}
responseData.addAll(data)
LoadResult.Page(
data = responseData,
prevKey = if (currentPage == 1) null else -1,
nextKey = currentPage.plus(1)
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Int, RepositoryResponse>): Int? {
return null
}
}
these log is showed correct data :
Log.d("RepoPagingSource",responseCode)
Log.d("RepoPagingSource",data.size.toString())
Log.d("RepoPagingSource",data.toString())
result of these logs :
RepoPagingSource: 200
RepoPagingSource: 2
RepoPagingSource: [RepositoryResponse(id=5246349....
but my recyclerview is empty and i checked the code in debug mode
here :
responseData.addAll(data)
data is null!
thanks in advance for your help
I have done it like :
class RepoPagingSource #Inject constructor(
private val repository: ApiRepository,
val context: Context ) : RxPagingSource<Int, RepositoryResponse>() {
private lateinit var sharedPref: SharedPref
override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, RepositoryResponse>> {
sharedPref = SharedPref(context)
var nextPageNumber = params.key
if (nextPageNumber == null) {
nextPageNumber = 1
}
return repository.getRepositories("bearer ${sharedPref.accessToken}", nextPageNumber)
.subscribeOn(Schedulers.io())
.map { response: Response<MutableList<RepositoryResponse>> -> response.body()?.let { toLoadResult(it, nextPageNumber) } }
.onErrorReturn { LoadResult.Error(it) }
}
private fun toLoadResult(
response: MutableList<RepositoryResponse>,
position:Int
): LoadResult<Int, RepositoryResponse> {
return LoadResult.Page(
response,
null,
position + 1,
COUNT_UNDEFINED,
COUNT_UNDEFINED
)
}
override fun getRefreshKey(state: PagingState<Int, RepositoryResponse>): Int? {
return null
}}
in its work for me ,also i have changed my ver of library Rx into rxjava2
I'm currently making a sample project about diagrams. I'm starting to use MVVM architecture recently, and I got stuck when the response is null. I also checked the Mutable Live Data to make sure that it is calling the API. Here's some of my code and the error-tag:
Model.kt
data class Model(
#SerializedName("FID") val FID: Int,
#SerializedName("region") val region: String,
#SerializedName("positive") val positive: Float
) {
}
ModelWrap.kt
data class ModelWrap(#SerializedName("samplesAPI") val attributes: Model){
}
ApiClient.kt
object ApiClient {
var retrofitService: ApiInterface? = null
const val BASE_URL = "https://sampleapi.../"
fun getApiSample() : ApiInterface {
if (retrofitService == null){
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
retrofitService = retrofit.create(ApiInterface::class.java)
}
return retrofitService!!
}
}
ApiInterface.kt
interface ApiInterface {
#GET("samples")
fun getSampleData(): Call<List<ModelWrap>>
}
MainViewModel.kt
class MainViewModelconstructor(private val repository: ModelRepository) : ViewModel(){
val sampleList= MutableLiveData<List<ModelWrap>>()
val errorMessage = MutableLiveData<String>()
fun getSampleData(pieChart: PieChart){
val response = repository.getSampleData()
response.enqueue(object : Callback<List<ModelWrap>> {
override fun onResponse(
call: Call<List<ModelWrap>>,
response: Response<List<ModelWrap>>
) {
sampleList.postValue(response.body())
}
override fun onFailure(call: Call<List<ModelWrap>>, t: Throwable) {
errorMessage.postValue(t.message)
}
})
}
}
MainViewModelFactory.kt
class MainViewModelFactoryconstructor(private val repository: MainRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(MainViewModel::class.java)){
MainViewModel(this.repository) as T
} else {
throw IllegalArgumentException("Sample ViewModel Not Found")
}
}
}
MainRepository.kt
class MainRepository constructor(private val retrofitService: ApiInterface){
fun getSampleData() = retrofitService.getSampleData()
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var pieChart: PieChart
lateinit var sampleViewModel: MainViewModel
private val sampleService = ApiClient.getApiSample()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
pieChart = findViewById(R.id.PieChart)
sampleViewModel= ViewModelProvider(this, MainViewModelFactory(MainRepository(sampleService))).get(MainViewModel::class.java)
getPieChart(pieChart)
}
private fun getPieChart(pieCharts: PieChart) {
mainViewModel.mainList.observe(this, Observer {
Log.d("TAG sample" , "onCreate PieChart: $it")
Log.d("Tag Samples Response" , response.body().toString())
if (it != null) {
val sampleEntries: List<PieEntry> = ArrayList()
for ((attributes) in it!!) {
sampleEntries.toMutableList()
.add(PieEntry(attributes.positive, attributes.region))
//........................................................................
val description = Description()
description.text = "Samples Data"
pieChart.description = description
pieChart.invalidate()
}
}
})
mainViewModel.errorMessage.observe(this, Observer { })
mainViewModel.getSampleData(pieCharts)
}
}
and Lastly, here's some or log message:
V/InputMethodManager: Starting input: tba=android.view.inputmethod.EditorInfo#8b795c0 nm : com.example.diargram ic=null
D/Tag Sample Response: null
D/TAG Sample: onCreate PieChart: null
E/libc: Access denied finding property "ro.serialno"
V/StudioTransport: Agent command stream started.
V/StudioTransport: Transport agent connected to daemon.
I would appreciate it if someone can help me :D, Thank you
Finally, I found a solution for my problem:
I type the wrong endpoint inside the interface class and it should be like this:
interface ApiInterface {
#GET("sample")
fun getSampleData(): Call<List> }
When it comes to assigning the livedata to the view, based on my JSON I should call ArrayList instead of List
List item
Before :
val sampleEntries: List = ArrayList()
After :
val sampleEntries: ArrayList<PieEntry> = ArrayList()
I'm trying it implement following Json string:
{
"msg":[
"football",
"cricket",
"baseball",
"rugby",
"gulf"
],
"status":"success"
}
I have created the data classes as below:
class Sports(
val msg : List<String>,
val status : String
)
And
class Msg (
val football : List<String>,
val cricket : List<String>,
val baseball : List<String>,
val rugby : List<String>,
val gulf : List<String>
)
Now I'm trying to get the objects and view it in a recyclerview list as per the tutorial.
How could I change it below & call it in the adapter?
interface PostApi {
/**
* Get the list of the pots from the API
*/
#GET("/posts")
fun getPosts(): Observable<List<Post>>
}
Edit:
MY adapter class as below:
class PostListAdapter: RecyclerView.Adapter<PostListAdapter.ViewHolder>() {
private lateinit var postList:Sports
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostListAdapter.ViewHolder {
val binding: ItemPostBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), R.layout.item_post, parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: PostListAdapter.ViewHolder, position: Int) {
holder.bind(postList)
}
override fun getItemCount(): Int {
//Getting error in .isInitialied 'Unresolved reference'
return if(::postList.isInitialized) postList.message.size else 0
}
fun updatePostList(postList: Sports){
this.postList = postList
notifyDataSetChanged()
}
class ViewHolder(private val binding:
ItemPostBinding):RecyclerView.ViewHolder(binding.root){ //Getting error in root 'Unresolved reference'
private val viewModel = PostViewModel()
fun bind(post: Sports){
viewModel.bind(post) //Getting error saying No value passed for parameter 'position'
binding.viewModel = viewModel
}
}
}
If you get the Json from server then call it like below:
interface SportsApi {
/**
* Get the Sports from the API
*/
#GET("/sports")
fun getSports(): Observable<Sports>
}
Or if you want to to check it in locally then you have to convert this Json
Using Gson:
val sports = Gson().fromJson(json, Sports::java.class)
Using Moshi:
val sports = Moshi.Builder().build().adapter(Sports::java.class).fromJson(json)
I am a beginner in Kotlin. I need to send a variable parameter from my Activity to a Retrofit call.
This is my call in on Create of Detail Activity
override fun onCreate(savedInstanceState: Bundle?) {
//...
val id = intent.getStringExtra("id")
// Get the ViewMode
val mModel = ViewModelProviders.of(this).get(myObjectViewModel::class.java)
//Create the observer which updates the UI.
val myObjectByIdObserver = Observer<MyObject> { myObject->
//...
}
//Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
mModel.getObjectById.observe(this, myObjectByIdObserver)
}
Here I insert value hardcode, I need the parameter received from the previous Activity.
class MyObjectViewModel : ViewModel() {
//this is the data that we will fetch asynchronously
var myObject: MutableLiveData<MyObject>? = null
val getMyObjectById: LiveData<MyObject>
get() {
if (myObject == null) {
myObject = MutableLiveData()
loadMyObjectById()
}
return myObject as MutableLiveData<MyObject>
}
private fun loadMyObjectById() {
val retrofit = Retrofit.Builder()
.baseUrl(Api.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val api = retrofit.create(Api::class.java)
val call = api.myObjectById(100)
call.enqueue(object : Callback<MyObject> {
override fun onResponse(call: Call<MyObject>, response: Response<MyObject>) {
myObject!!.value = response.body()
}
override fun onFailure(call: Call<MyObject>, t: Throwable) {
var tt = t
}
})
}
My API:
interface Api {
companion object {
const val BASE_URL = "https://.../"
}
#GET("myObjects/{id}")
fun myObjectById(#Path("id") id: Int?): Call<MyObject>
}
You can do this by ``#Query``` annotation.
interface Api {
companion object {
const val BASE_URL = "https://.../"
}
#GET("myObjects/{id}")
fun myObjectById(#Path("id") id: Int?, #Query("a_param") aParam: String?): Call<MyObject>
}
Edited. I completely misread your intension.
What you need seems to be ViewModelProvider.NewInstanceFactory like
class MyObjectViewModel(val id: Int): ViewModel() {
class Factory(val id: Int) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MyObjectViewModel(id) as T
}
}
}
then
val myViewModel = ViewModelProviders
.of(this, MyObjectViewModel.Factory(id))
.get(MyObjectViewModel::class.java)