parse JSON object using retrofit in kotlin - android

I am trying to show json data using retrofit library in kotlin
This is my Json:
[
{
"login": "mojombo",
"id": 1,
},
{
"login": "defunkt",
"id": 2,
}
]
My Main activity
call.enqueue(object : Callback<UserResponse> {
override fun onResponse(call: Call<UserResponse>, response: Response<UserResponse>) {
Log.e("list","list")
val countrylist = response.body()
for (size in response.body()) {
System.out.println(size.toString())
}
// var listOfMovies: List<UserResponse> = response.body()?.results!!
// myCustomAdapter = UserListAdapter(applicationContext, listOfMovies)
// recyclerView.setAdapter(myCustomAdapter)
progressBar.visibility = View.GONE
}
override fun onFailure(call: Call<UserResponse>?, t: Throwable?) {
progressBar.visibility = View.GONE
Log.e("list", t.toString())
}
})

This an app that I build in kotlin using retrofit and rxjava in the best way possible using a test API.
Model
data class Post( val userID:Int, val title:String, val body: String)
Retrofit Package
IMyApi interface
interface IMyApi {
#get:GET("posts")
val posts: Observable<List<Post>>
}
RetrofitClient Object class
object RetrofitClient {
val instance: Retrofit by lazy {
Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
Adapter Package
PostAdapter class
class PostAdapter(private val context: Context, private val postList: List<Post>)
:RecyclerView.Adapter<PostViewHolder>()
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
PostViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.post_item, parent, false)
return PostViewHolder(itemView)
}
override fun getItemCount(): Int {
return postList.size
}
override fun onBindViewHolder(holder: PostViewHolder, position: Int)
{
holder.userId.text = postList[position].userID.toString()
holder.title.text = postList[position].title
holder.body.text = StringBuilder(postList[position].body.substring(0,20))
.append("...").toString()
}
}
PostViewHolder class
class PostViewHolder(itemView: View):RecyclerView.ViewHolder(itemView) {
var userId = itemView.txtID
var title = itemView.txtTitle
var body = itemView.txtBody
}
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var jsonApi: IMyApi
private var compositeDisposable: CompositeDisposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Init api
val retrofit = RetrofitClient.instance
jsonApi = retrofit.create(IMyApi::class.java)
// View
recycler_posts.layoutManager = LinearLayoutManager(this)
recycler_posts.setHasFixedSize(true)
fetchData()
}
private fun fetchData() {
compositeDisposable.add(jsonApi.posts
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { posts->displayData(posts)})
}
private fun displayData(posts: List<Post>?) {
val adapter = PostAdapter(this, posts!!)
recycler_posts.adapter = adapter
}
}
Using this as displayed above should help you solve your issue hopefully. Also when in the code you come across "recycler_posts". This is a id to the recycler added in activity_main. If you need me to include that let me know

That's what we have on our app
object GetFAQsAPI {
private val LOG_TAG = GetFAQsAPI.javaClass.simpleName
interface ThisCallback {
fun onSuccess(getFAQs: GetFAQs)
fun onFailure(failureMessage: String)
fun onError(errorMessage: String)
}
/* POST */
fun postData(jo: JsonObject, callback: GetFAQsAPI.ThisCallback) {
val call = Service.getService().get_faqs(jo)
call.enqueue(object : Callback<JsonObject> {
override fun onResponse(call: Call<JsonObject>, response: Response<JsonObject>) {
//Log.e(LOG_TAG, response.body().toString());
try {
if (response.body()?.get("success")!!.asBoolean) {
val gson = GsonBuilder().setPrettyPrinting().create()
val getFAQs = gson.fromJson(response.body(), GetFAQs::class.java)
callback.onSuccess(getFAQs)
} else {
Log.e(LOG_TAG, "else")
val error = response.body()!!.get("err").asString
callback.onError(error)
}
} catch (e: Exception) {
Log.e(LOG_TAG, "exception" + e.localizedMessage)
callback.onFailure(e.message!!)
}
}
override fun onFailure(call: Call<JsonObject>, t: Throwable) {
Log.e(LOG_TAG, "onFailure: " + t.message)
callback.onFailure(t.message!!)
}
})
}
}
That's how we call it from our fragment - getFAQs is the object parsed.
private fun getFAQsAPI() {
showLoading(true)
val jo = JsonObject().apply {
addProperty("faq_category", "admin")
}
GetFAQsAPI.postData(jo, object : GetFAQsAPI.ThisCallback {
override fun onSuccess(getFAQs: GetFAQs) {
Log.i(LOG_TAG, "onSuccess")
showLoading(false)
updateUI(getFAQs)
}
override fun onFailure(failureMessage: String) {
Log.e(LOG_TAG, failureMessage)
}
override fun onError(errorMessage: String) {
Log.e(LOG_TAG, errorMessage)
}
})
}
Hope that helps.

Related

How to make recyclerview populate after response finishes

I'm having a problem with "UninitializedPropertyAccessException", I believe it's because the recyclerview is trying to be populated before the response finishes the process, does anyone know how I can solve it? P.S: This is causing my published app to crash, but in the emulator it works normally
TibiaApiService:
class TibiaApiService {
companion object {
private const val BASE_URL = "https://api.tibiadata.com"
private var retrofit: Retrofit? = null
fun getInstance(): Retrofit {
val okHttpClient: OkHttpClient = OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.build()
if (retrofit == null) {
retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
}
return retrofit!!
}
}}
NewsArchiveFragment:
class NewsArchiveFragment : Fragment() {
private lateinit var binding: FragmentNewsArchiveBinding
private lateinit var newsModel: List<NewsModel>
private lateinit var newsFromId: NewsFromIdModel
private lateinit var progressDialog: ProgressDialog
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentNewsArchiveBinding.inflate(layoutInflater, container, false)
progressDialog = ProgressDialog(activity)
getNews()
return binding.root
}
private fun getNews() = with(binding) {
try {
progressDialog.setMessage("Loading...")
progressDialog.show()
val apiService = TibiaApiService.getInstance().create(TibiaApiInterface::class.java)
apiService.getNewsArchive().enqueue(object : retrofit2.Callback<NewsResponse>,
NewsArchiveAdapter.ClickNews {
override fun onResponse(
call: Call<NewsResponse>,
response: Response<NewsResponse>
) {
if(response.isSuccessful) {
newsModel = response.body()!!.news
populateRV(newsModel, this)
}
}
override fun onFailure(call: Call<NewsResponse>, t: Throwable) {
progressDialog.dismiss()
Snackbar.make(root, t.message.toString(), Snackbar.LENGTH_LONG)
.show()
}
override fun clickNews(news: NewsModel) {
loadCompleteNews(news.id)
}
})
} catch (e: SocketTimeoutException) {
Snackbar.make(root, e.message.toString(), Snackbar.LENGTH_LONG)
.show()
}
}
private fun populateRV(
newsModel: List<NewsModel>,
clickNews: NewsArchiveAdapter. ClickNews
) = with(binding) {
rvNews.layoutManager = LinearLayoutManager(activity)
rvNews.setHasFixedSize(true)
rvNews.adapter = NewsArchiveAdapter(newsModel, clickNews)
rvNews.viewTreeObserver.addOnGlobalLayoutListener {
progressDialog.dismiss()
}
}
private fun loadCompleteNews(id: Int) = with(binding) {
val apiService = TibiaApiService.getInstance().create(TibiaApiInterface::class.java)
apiService.getNewsArchiveFromId(id)
.enqueue(object : retrofit2.Callback<NewsFromIdResponse> {
override fun onResponse(
call: Call<NewsFromIdResponse>,
response: Response<NewsFromIdResponse>
) {
newsFromId = response.body()!!.newsFromId
onShowDialog(newsFromId)
}
override fun onFailure(call: Call<NewsFromIdResponse>, t: Throwable) {
Snackbar.make(root, t.message.toString(), Snackbar.LENGTH_LONG).show()
}
})
}
private fun onShowDialog(newsFromId: NewsFromIdModel) {
MaterialAlertDialogBuilder(requireContext())
.setTitle(newsFromId.date)
.setMessage(newsFromId.content)
.setNegativeButton(getString(R.string.close_dialog)) { dialog, _ ->
dialog.dismiss()
}
.show()
}}
NewsArchiveAdapter:
class NewsArchiveAdapter(
private val news: List<NewsModel>,
private val clickNews: ClickNews
) : RecyclerView.Adapter<NewsArchiveAdapter.ViewHolder>() {
interface ClickNews{
fun clickNews(news: NewsModel)
}
inner class ViewHolder(val binding: RvNewsListBinding) :
RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
RvNewsListBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val _news = news[position]
holder.binding.apply {
txtNewsNews.text = _news.news
txtNewsDate.text = _news.date
if (position % 2 == 0) {
cardViewNews.setBackgroundColor(
root.resources.getColor(R.color.very_light_brown)
)
} else {
cardViewNews.setBackgroundColor(
root.resources.getColor(R.color.light_brown)
)
}
cardViewNews.setOnClickListener { clickNews.clickNews(_news) }
}
}
override fun getItemCount(): Int = news.size}

Displaying images in recyclerview retrieved from retrofit

I'm new in android development and building a movie app in which I'm using recyclerview to display movie images and using retrofit to fetch data from themoviedb.org API.
I'm successfully getting the data from the mention URL but not able to display it on the screen.
GenreModel class:
data class Genre (
var genres: ArrayList<GenreList>
): Serializable
data class GenreList (
val id: Int,
val name: String,
val results: ArrayList<Results>
): Serializable
data class Results (
var id: Int,
var title: String,
var poster_path: String,
var release_date: String,
var original_language: String,
var overview: String
): Serializable
Function to get movie details from API:
private fun getGenreName() {
val retrofit = RetrofitClient.getInstance()
val apiInterface = retrofit.create(ApiInterface::class.java)
val genreListCall: Call<Genre> = apiInterface.getGenreDetails(Constants.APP_ID)
genreListCall.enqueue(object: Callback<Genre> {
override fun onResponse(call: Call<Genre>, response: Response<Genre>) {
if(response.isSuccessful) {
val result: Genre? = response.body() // returns genres object which contains ID and Name
val genres: ArrayList<GenreList> = result!!.genres// saves ID and Names of genres
for(x in genres) {
val id = x.id
val name = x.name // returns the genre names. Ex- action, family, drama....
val listCall: Call<GenreList> = apiInterface.getGenreMovieDetails(id, Constants.APP_ID)
listCall.enqueue(object: Callback<GenreList> {
override fun onResponse(call: Call<GenreList>, response: Response<GenreList>) {
if(response.isSuccessful) { // returns id, results[]
val result1: GenreList? = response.body()
results = result1!!.results // results[id, title, poster, language....]
}
}
override fun onFailure(call: Call<GenreList>, t: Throwable) {
Log.e("ERRORInGenreMovieList", t.message.toString())
}
})
genreList.add(GenreList(id, name, results))
genreCategories.add(name)
}
} else {
val rc = response.code()
when (rc) {
400 -> {
Log.e("Error 400", "Bad Connection")
}
404 -> {
Log.e("Error 404", "Not Found")
}
else -> {
Log.e("Error", "Generic Error")
}
}
}
}
override fun onFailure(call: Call<Genre>, t: Throwable) {
Log.e("ERROR in Genre Name", t.message.toString())
}
})
}
GenreAdapter for recyclerview:
class GenreFragmentAdapter(private val genreList: ArrayList<GenreList>, val context: Context): RecyclerView.Adapter<GenreFragmentAdapter.ViewHolder>() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var genreName:TextView = view.findViewById(R.id.genreName)
var genreListRecyclerView: RecyclerView = view.findViewById(R.id.genreListRecyclerView)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view: View = LayoutInflater.from(context).inflate(R.layout.genre_list_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val name = genreList[position].name
val results = genreList[position].results
holder.genreName.text = name
println("Name: $name")
setItemRecyclerView(holder.genreListRecyclerView, results)
println("Results: $results")
}
override fun getItemCount(): Int {
return genreList.size
}
private fun setItemRecyclerView(recyclerView: RecyclerView, results: ArrayList<Results>) {
val genreListAdapter = GenreResultsAdapter(results, context)
recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
recyclerView.adapter = genreListAdapter
}
}
BaseUrl: https://api.themoviedb.org
ApiInterface:
interface ApiInterface {
#GET("/3/genre/movie/list") // returns genre list
fun getGenreDetails(
#Query("api_key") api_key: String
): Call<Genre>
#GET("/3/genre/{id}/movies") // returns movie details according to specified genre
fun getGenreMovieDetails(
#Path("id") id: Int,
#Query("api_key") api_key: String
): Call<GenreList>
#GET("/3/movie/upcoming") // returns upcoming movies
fun getUpcomingMovies(
#Query("api_key") api_key: String
): Call<GenreList>
#GET("/3/movie/{id}")
fun getMovie(
#Path("id") id: Int,
#Query("api_key") api_key: String
): Call<Results>
}
RetrofitClient:
object RetrofitClient {
fun getInstance(): Retrofit {
return Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(getUnsafeOkHttpClient()!!.build())
.build()
}
private fun getUnsafeOkHttpClient(): OkHttpClient.Builder? {
return try {
// Create a trust manager that does not validate certificate chains
val trustAllCerts = arrayOf<TrustManager>(
object : X509TrustManager {
#Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<X509Certificate?>?, authType: String?) {
}
#Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<X509Certificate?>?, authType: String?) {
}
override fun getAcceptedIssuers(): Array<X509Certificate?>? {
return arrayOf()
}
}
)
// Install the all-trusting trust manager
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, SecureRandom())
// Create an ssl socket factory with our all-trusting manager
val sslSocketFactory = sslContext.socketFactory
val builder = OkHttpClient.Builder()
builder.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
builder.hostnameVerifier { _, _ -> true }
builder
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}

Why data is not loaded to the RecyclerView MVVM?

I have an android application that requests a list from the network and should display it in a recycler view but this does not happen. There are no errors in the logcat. I think the problem is in my function in ViewModel. Help me to understand
My CategoryClient class:
class CategoryClient {
companion object {
const val KEY = "5de979d34658275ac9dc2375"
}
var category: List<Category>? = null
fun loadCategory(): List<Category>? {
// x-apikey interceptor for restdb API
fun createOkHttpClient(): OkHttpClient? {
val httpClient = OkHttpClient.Builder()
httpClient.addInterceptor(object : Interceptor {
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
val original = chain.request()
val originalHttpUrl = original.url
val url = originalHttpUrl.newBuilder()
.addQueryParameter("apikey", KEY)
.build()
val requestBuilder = original.newBuilder()
.url(url)
val request = requestBuilder.build()
return chain.proceed(request)
}
})
// logging interceptor
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.BODY
httpClient.addInterceptor(logging)
return httpClient.build()
}
val retrofit = Retrofit.Builder()
.baseUrl("https://testcategory-d6d7.restdb.io/rest/")
.addConverterFactory(GsonConverterFactory.create())
.client(createOkHttpClient())
.build()
val api = retrofit.create(CategoryApi::class.java)
api.fetchAllCategory().enqueue(object : Callback<List<Category>> {
override fun onFailure(call: Call<List<Category>>, t: Throwable) {
}
override fun onResponse(call: Call<List<Category>>, response: Response<List<Category>>) {
//Log.d(TAG, "onResponse: ${response.body()!![0].name}")
category = response.body()!!
//presenter.setupCategoryList(categoryList = category as ArrayList<Category>)
}
})
return category
}
}
My Category Activity class
val binding: ActivityCategoryBinding =
DataBindingUtil.setContentView(this#CategoryActivity, R.layout.activity_category)
val categoryViewModel =
ViewModelProviders.of(this#CategoryActivity).get(CategoryViewModel::class.java)
binding.categoryViewModel = categoryViewModel
categoryViewModel.getArrayList().observe(this#CategoryActivity, Observer { category ->
mAdapter = CategoryAdapter(this#CategoryActivity, categoryList = category )
recycler_category.layoutManager = LinearLayoutManager(applicationContext, OrientationHelper.VERTICAL, false)
recycler_category.adapter = mAdapter
recycler_category.setHasFixedSize(true)
})
My RecyclerView Adapter class
class CategoryAdapter(private val context: Context, private val categoryList: ArrayList<Category>?)
: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mCategoryList: ArrayList<Category> = ArrayList()
private var mSourceList: ArrayList<Category> = ArrayList()
fun setupCategory(categoryList: ArrayList<Category>) {
mSourceList.clear()
mSourceList.addAll(categoryList)
search(query = "")
}
fun search(query: String) {
mCategoryList.clear()
mSourceList.forEach {
if (it.name.contains(query, ignoreCase = true)) {
mCategoryList.add(it)
}
}
notifyDataSetChanged()
}
fun sortByName() {
mCategoryList.sortBy { it.name }
notifyDataSetChanged()
}
fun sortByPrice() {
mCategoryList.sortBy { it.price }
notifyDataSetChanged()
}
fun filter() {
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is CategoryViewHolder) {
holder.bind(categoryModel = mCategoryList[position])
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val itemView = layoutInflater.inflate(R.layout.cell_category, parent, false)
return CategoryViewHolder(itemView = itemView)
}
override fun getItemCount(): Int {
return mCategoryList.count()
}
class CategoryViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
var mCategoryIcon: CircleImageView = itemView.findViewById(R.id.category_icon)
var mCategoryName: TextView = itemView.findViewById(R.id.category_name)
var mCategoryPrice: TextView = itemView.findViewById(R.id.category_price)
private var mCategoryType: TextView = itemView.findViewById(R.id.category_type)
fun bind(categoryModel: Category) {
categoryModel.icon.let { url ->
Picasso.with(itemView.context).load(url)
.into(mCategoryIcon)
}
mCategoryName.text = categoryModel.name
mCategoryPrice.text = categoryModel.price.toString()
mCategoryType.text = categoryModel.category
}
}
}
And my ViewModel class
class CategoryViewModel : ViewModel() {
var mutableLiveData = MutableLiveData<ArrayList<Category>>()
fun getArrayList(): MutableLiveData<ArrayList<Category>> {
mutableLiveData.value = CategoryClient().loadCategory() as ArrayList<Category>?
return mutableLiveData
}
}
Your data is not updated from asynchronous API call. Change you implementation like below to get updated data:
class CategoryViewModel : ViewModel() {
fun getArrayList(): MutableLiveData<ArrayList<Category>> {
return CategoryClient().loadCategory()
}
}
And your loadCategory
var mutableLiveData = MutableLiveData<ArrayList<Category>>()
fun loadCategory(): MutableLiveData<ArrayList<Category>> {
api.fetchAllCategory().enqueue(object : Callback<List<Category>> {
override fun onFailure(call: Call<List<Category>>, t: Throwable) {
}
override fun onResponse(call: Call<List<Category>>, response: Response<List<Category>>) {
mutableLiveData.postValue(response.body())
}
})
return mutableLiveData
}
And your adapter should be corrected to use single source
class CategoryAdapter(private val context: Context, private val mCategoryList: ArrayList<Category>?)
: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mSourceList: ArrayList<Category> = ArrayList(mCategoryList)
.....
}

callback.onResult() not returning value to adapter in PageKeyedDataSource<Int, Item>() in android

I'm using android architecture component in my project.So i used paging library for pagination, in my case i needed PageKeyedDataSource, but i could not able to get the loaded data back to the UI,
Codes are
DataSourceFactory.kt
class DataSourceFactory : DataSource.Factory<Int, Item>() {
private val mutableLiveData = MutableLiveData<SearchedItemsDataSource>()
private lateinit var feedDataSource: SearchedItemsDataSource
override fun create(): DataSource<Int, Item> {
feedDataSource = SearchedItemsDataSource()
mutableLiveData.postValue(feedDataSource)
return feedDataSource
}}
DataSource.kt
class SearchedItemsDataSource() : PageKeyedDataSource<Int, Item>() {
private var client: Api
init {
val logging = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
val httpClient = OkHttpClient.Builder()
httpClient.addInterceptor(logging)
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://api.stackexchange.com/2.2/")
.client(httpClient.build())
.build()
client = retrofit.create(Api::class.java)
}
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Item>) {
val callClient = client.getAnswers(1, params.requestedLoadSize, "stackoverflow")
callClient.enqueue(object : Callback<StackApiResponse> {
override fun onResponse(call: Call<StackApiResponse>, response: Response<StackApiResponse>) {
if (response.isSuccessful && response.body() != null) {
println("loadInitial onResponse1111 :${response.isSuccessful} *** ${params.requestedLoadSize}")
val searchedItems = response.body() as StackApiResponse
callback.onResult(searchedItems.items, 0,searchedItems.items.size,null, 21)
println("loadInitial onResponse1111 :${response.isSuccessful} *** ${params.requestedLoadSize} *** ${searchedItems.items.size}")
} else {
println("loadInitial onResponse :${response.isSuccessful} *** ${response.message()}")
}
}
override fun onFailure(call: Call<StackApiResponse>, t: Throwable) {
t.printStackTrace()
println("loadInitial onFailure :${t.message} ${t.localizedMessage}")
}
})
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Item>) {
println("loadAfter " + params.key + " Count " + params.requestedLoadSize);
val callClient = client.getAnswers(params.key, params.requestedLoadSize, "stackoverflow")
callClient.enqueue(object : Callback<StackApiResponse> {
override fun onResponse(call: Call<StackApiResponse>, response: Response<StackApiResponse>) {
if (response.isSuccessful && response.body() != null) {
println("loadAfter onResponse :${response.isSuccessful} *** ${params.key}")
val nextKey = params.key + 1
val searchedItems = response.body() as StackApiResponse
callback.onResult(searchedItems.items, nextKey)
} else {
println("loadAfter onResponse :${response.isSuccessful} *** ${response.message()}")
}
}
override fun onFailure(call: Call<StackApiResponse>, t: Throwable) {
t.printStackTrace()
println("loadAfter onFailure :${t.message} ${t.localizedMessage}")
}
})
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, Item>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}}
ViewModel.kt
class MainViewModel : ViewModel() {
internal lateinit var itemList: LiveData<PagedList<Item>>
fun getPagedItems(): LiveData<PagedList<Item>> {
val executor = Executors.newFixedThreadPool(5)
val feedDataFactory = DataSourceFactory()
val pagedListConfig = PagedList.Config.Builder()
.setEnablePlaceholders(true)
.setInitialLoadSizeHint(20)
.setPageSize(20).build()
itemList = (LivePagedListBuilder(feedDataFactory, pagedListConfig)).build()
return itemList
}}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: MainViewModel
private lateinit var mAdapter: ItemAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
mAdapter = ItemAdapter(this)
binding.recyclerView.adapter = mAdapter
viewModel.getPagedItems().observe(this, Observer { items ->
if (items != null && items.isNotEmpty()) {
println("observe called 1 ${items.size}")
mAdapter.submitList(items)
} else {
println("observe called 2 ${items?.size}")
}
})
}
}
In your Adapter, you should not be managing the list manually since you are using PagedListAdapter.
In your #Override public int getItemCount() {} do:
#Override
public int getItemCount() {
return super.getItemCount()
}

Android Architecture components: ViewModel returns null on configuration change

I`m developing a gallery app in kotlin using pixabay api, implementing Android Architecture components and retrofit to interact with the server.
the app works fine but when i change the configuration the view model returns null! what is the problem ?
interface PhotoApi {
#GET(context.getString(R.string.api_key))
fun getPhotos(): Call<PhotoList>
}
Retrofit setup
object PhotoRetriever {
val BASE_URL = "https://pixabay.com/api/"
val service: PhotoApi
init {
val retrofit = Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create()).build()
service = retrofit.create(PhotoApi::class.java)
}
}
class MainActivity : AppCompatActivity() {
//var photoList: List<Photo>? = null
lateinit var viewModel: MainActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this#MainActivity).get(MainActivityViewModel::class.java)
viewModel.getList().observe(this, object :Observer<List<Photo>> {
override fun onChanged(t: List<Photo>?) {
textview.setText(t?.get(1)?.webFormatUrl)
}
})
the view model class
class MainActivityViewModel : ViewModel() {
private val repository: Repository
lateinit var photoList: LiveData<List<Photo>>
val itemsListObservable: MediatorLiveData<List<Photo>>
init {
repository = Repository()
itemsListObservable = MediatorLiveData()
}
fun getList(): LiveData<List<Photo>> {
photoList = repository.getNetworkData()
Log.i("RECEIVED","size is ${photoList.value?.size} from viewModel ")
return photoList
}
}
the Repository class
class Repository {
val photoApi: PhotoApi
var photoList: List<Photo>?
val retlist = MutableLiveData<List<Photo>>()
init {
photoApi = PhotoRetriever.service
photoList = ArrayList<Photo>()
}
fun getItemsListFromWeb() {
photoApi.getPhotos().enqueue(object : Callback<PhotoList> {
override fun onFailure(call: Call<PhotoList>?, t: Throwable?) {
// Log.i("RECEIVED","${t?.message} from repository ")
// retlist.value = null
}
override fun onResponse(call: Call<PhotoList>?, response: Response<PhotoList>?) {
if (response!!.isSuccessful) {
retlist.value = response.body()?.hits
} else {
retlist.value = null
}
Log.i("RECEIVED", "size is ${retlist.value?.size} from repository ")
}
})
}
fun getNetworkData(): LiveData<List<Photo>> {
getItemsListFromWeb()
return retlist
}
}
i have solved the problem, the problem was in retrofit onResponse callback method
i remove the else block and everything works fine
before
override fun onResponse(call: Call<PhotoList>?, response: Response<PhotoList>?) {
if (response!!.isSuccessful) {
retlist.value = response.body()?.hits
} else {
retlist.value = null
}
Log.i("RECEIVED", "size is ${retlist.value?.size} from repository ")
}
after
override fun onResponse(call: Call<PhotoList>?, response: Response<PhotoList>?) {
if (response!!.isSuccessful) {
retlist.value = response.body()?.hits
}
Log.i("RECEIVED", "size is ${retlist.value?.size} from repository ")
}

Categories

Resources