Populate Listview from Google Books JSON - android

I'm using Google Books API for searching books. But problem is that when I want my ListView to be populated with books, I get an error. This error is pointed onPostExecute, but I can't figured out what's the problem.
kotlin.KotlinNullPointerException
at com.example.claudiu.reader.Fragments.ISBNFragment$FetchBookTask.onPostExecute(ISBNFragment.kt:137)
at com.example.claudiu.reader.Fragments.ISBNFragment$FetchBookTask.onPostExecute(ISBNFragment.kt:56)
Here is where I set my adapter :
override fun onPostExecute(books: List<Book>?) {
if (books != null) {
adapter!!.clear()
for (book in books) {
adapter!!.add(book)
}
}
}
And here is all code where I'm parsing the JSON:
#Throws(JSONException::class)
private fun getBookDataFromJson(booksJsonStr: String?): List<Book> {
val books = ArrayList<Book>()
val API_RESULT_ITEMS_ARRAY = "items"
val API_VOLUME_INFO = "volumeInfo"
val API_BOOK_TITLE = "title"
val API_BOOK_IMAGE_LINKS = "imageLinks"
val API_BOOK_SMALL_THUMBNAIL = "smallThumbnail"
val API_BOOK_AUTHORS_ARRAY = "authors"
val booksJson = JSONObject(booksJsonStr)
val itemsArray = booksJson.getJSONArray(API_RESULT_ITEMS_ARRAY)
for (i in 0 until itemsArray.length()) {
val item = itemsArray.getJSONObject(i)
val volumeInfo = item.getJSONObject(API_VOLUME_INFO)
val bookTitle = volumeInfo.getString(API_BOOK_TITLE)
val imageLinksSB = StringBuilder()
if (!volumeInfo.isNull(API_BOOK_IMAGE_LINKS)) {
val imageLinks = volumeInfo.getJSONObject(API_BOOK_IMAGE_LINKS)
imageLinksSB.append(imageLinks.getString(API_BOOK_SMALL_THUMBNAIL))
} else {
imageLinksSB.append("-1")
}
val bookImageLink = imageLinksSB.toString()
val authorsSB = StringBuilder()
if (!volumeInfo.isNull(API_BOOK_AUTHORS_ARRAY)) {
val authorsArray = volumeInfo.getJSONArray(API_BOOK_AUTHORS_ARRAY)
for (k in 0 until authorsArray.length()) {
authorsSB.append(authorsArray.getString(k))
authorsSB.append(getString(R.string.comma))
}
} else {
authorsSB.append(getString(R.string.unknown_error))
}
val bookAuthors = authorsSB.toString()
books.add(Book(bookTitle, bookAuthors, bookImageLink))
}
Log.d(LOG_TAG, "BOOKS : $books")
return books
}
I couldn't find any thing to help me and I have no idea what should I do.

Your problem is the place where you declare your adapter, I had this problem because declaration of my adapter was in the wrong place.
You should move adapter declaration from onCreatView into onViewCreated and everything will work fine.
Hope it helps !

Related

Groupie RecyclerView OnClick returns empty Item

I am using a room database with live data
Retrieving information from Database
private fun getData(query: String) {
var dataIssueJson: MutableList<DataIssue>
dataViewModel.searchDatabase(query).observe(this, {
dataList = it as MutableList<Data>
data = if (dataList.isNotEmpty())
dataList[0] else
Data()
val gson = Gson()
val dataIssueString =
if (dataList.isNotEmpty()) {
dataList[0].dataIssue
} else ""
val type = object : TypeToken<MutableList<DataIssue>>() {}.type
dataIssueJson = if (dataIssueString.isNotEmpty())
gson.fromJson(dataIssueString, type)
else
mutableListOf()
viewAdapter.clear()
initRecyclerView(dataIssueJson.toDataIssueItem())
val dataStatus = if (dataList.isNotEmpty())
dataList[0].dataStatus
else "Status"
dataViewModel.dataStatus.value = dataStatus
colorStatus(dataStatus)
})
}
Recyclerview
private fun initRecyclerView(dataItem: List<dataIssueItem>) {
binding.recyclerViewDataIssues.apply {
layoutManager = LinearLayoutManager(this#MainActivity)
adapter = viewAdapter.apply {
addAll(botItem)
setOnItemClickListener(onClickItem)
notifyDataSetChanged()
}
scrollToPosition(binding.recyclerViewDataIssues.adapter!!.itemCount - 1)
}
}
private fun List<DataIssue>.toDataIssueItem(): List<DataIssueItem> {
return this.map { DataIssueItem(it) }
}
OnClick
private val onClickItem = OnItemClickListener { item, view ->
if (item is DatatIssueItem) {
Log.d(AppConstants.MAIN_ACTIVITY_TAG, "DataIssue:${item.dataIssue.dataIssue}, date&time: ${item.dataIssue.dateAndTimeOfIssue}")
}
}
The Recyclerview works fine, but when I click on the Recyclerview Item it returns an empty Item and I'm just not sure why
Yeah, I figured it out. The data comes from the Item itself, so make sure to let the data be accessible from the Item.

Kotlin returns object with unassigned properties after assigning them in apply function

I'm trying to do a quite simple task: assign properties to an object and return that same object after retrieving the infos with a REST call.
In my runBlocking block I use the apply function to change the properties of my object, but after trying different ways to assign them, instantiate the object itself, modifying constructing logic of the object, I still get an object with the default values.
Here's my Info object:
class DrivingLicenceInfo {
var type : String = ""
var nationality : String = ""
var number : String = ""
var releaseDate : String = ""
var expiryDate : String = ""
}
Here's the method which gives me problems:
private fun getDerivingLicenceInfoAndWaitForCompletion(): DrivingLicenceInfo {
return runBlocking {
val response = retrieveDrivingLicenceInfoAsync().await()
if (response.isSuccessful) {
var info = DrivingLicenceInfo()
response.body()?.let {
info.apply {
it.data.let { data ->
val type = data.guy
val drivingLicenseNationality = data.drivingLicenseNationality
val drivingLicenseNumber = data.drivingLicenseNumber
val drivingReleaseDate = data.drivingReleaseDate
val drivingExpiryDate = data.drivingExpiryDate
this.type = type
this.nationality = drivingLicenseNationality
this.number = drivingLicenseNumber
this.releaseDate = drivingReleaseDate
this.expiryDate = drivingExpiryDate
}
}
info
Log.i("driving.info.call", info.type)
}
}
DrivingLicenceInfo()
}
}
And here's where I use it, in my Main, and where I get an info object with empty strings as properties
private void getDrivingLicenceData() {
DrivingLicenceInfoService service = new DrivingLicenceInfoServiceImpl(context);
DrivingLicenceInfo info = service.getDrivingLicenceInfo();
Log.i("driving.info.main",info.getType());
profileViewModel.licenceNumber.postValue(info.getNumber());
profileViewModel.licenceExpiryDate.postValue(info.getExpiryDate());
}
The log in the runBlocking correctly shows the property, the log in my Main doesn't even show up.
Using the debugger I am able to see that info has empty strings as value.
Could somebody help me to figure out what I'm doing wrong?
Thank you
Beside #JeelVankhede giving you the main reason for your problem, I suggest some minor code improvements as well. I personally feel this is ways less verbose and better readable
private fun getDrivingLicenceInfoAndWaitForCompletion(): DrivingLicenceInfo {
return runBlocking {
val response = retrieveDrivingLicenceInfoAsync().await()
var info = DrivingLicenceInfo()
return if (response.isSuccessful) {
response.body()?.let {
info.apply {
type = it.data.guy
nationality = it.data.drivingLicenseNationality
number = it.data.drivingLicenseNumber
releaseDate = it.data.drivingReleaseDate
expiryDate = it.data.drivingExpiryDate
}
Log.i("driving.info.call", info.type)
info
} ?: info
} else { info }
}
}
Since #JeelVankhede already told you the main reason of your problem and I also have some suggestions apart from the one given by #WarrenFaith.
If DrivingLicenceInfo is a model class you can declare it as data class like
data class DrivingLicenceInfo (
val type : String = "",
val nationality : String = "",
val number : String = "",
val releaseDate : String = "",
val expiryDate : String = ""
)
you can read more about data class here.
And then you can write your function as
private fun getDerivingLicenceInfoAndWaitForCompletion(): DrivingLicenceInfo {
val info = runBlocking {
val response = retrieveDrivingLicenceInfoAsync().await()
if (response.isSuccessful) {
response.body()?.let {
it.data.let { data ->
DrivingLicenceInfo(
type = data.guy,
nationality = data.drivingLicenseNationality,
number = data.drivingLicenseNumber,
releaseDate = data.drivingReleaseDate,
expiryDate = data.drivingExpiryDate
)
}
} ?: DrivingLicenceInfo()
} else {
DrivingLicenceInfo()
}
}
Log.i("driving.info.call", info.type)
return info
}

How to show a progress during fetching JSON-data

I would like to fetch some data from my web server. The data is a JSON object. I fetch the data with the fuel framework. I want to fill a recyclerview with the data. But I want to show a progress until the data is fetched.
But I have no idea how to solve this with fuel.
I have studied the fuel documentation. But I cannot find a solution
The code fetches some JSON data
fun fetchMyThings(): List<Thing> {
val username = "xxxx"
val password = "xxxxxxxxx"
val things = mutableListOf<Thing>()
Fuel.get("https://www.thingurl.com/things")
.authentication()
.basic(username, password)
.responseJson { request, response, result ->
request.responseProgress()
val arr: JSONArray = result.get().array()
println(arr.length())
for (i in 0 until arr.length()) {
var elem: JSONObject = arr[i] as JSONObject
var obj: JSONObject = elem
var thing = Thing(UUID.fromString(obj.getString("uuid")))
thing.summary = obj.getString("summary")
things += thing
println(thing)
}
}
return things
}
This code fills the recyclerview
private fun fillRecyclerView(things : List<Thing>) {
val recyclerView = findViewById<RecyclerView>(R.id.main_recycler)
val mainActivityRecyclerAdapter = MainActivityAdapter(this, things)
recyclerView.adapter = mainActivityRecyclerAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
}
Expectation: Progresshandling with Fuel
Many thanks in advance for any help
Next try...
fun getThings(): Single<Result<String, FuelError>> {
val username = "********"
val password = "********"
val http = "https://www.nowhere.com/thing/"
.httpGet()
.authentication()
.basic(username, password)
.rxString(Charsets.UTF_8)
return http
}
class MainActivity : AppCompatActivity() {
var things: List<Thing> = emptyList()
protected lateinit var adapter: MainActivityAdapter
override fun onCreate(savedInstanceState: Bundle?) {
//....
fillRecyclerView(things)
Server.getThings().subscribe { p ->
println(p)
val arr: JSONArray = JSONArray(p.component1())
for (i in 0 until arr.length()) {
var elem: JSONObject = arr[i] as JSONObject
var obj: JSONObject = elem
var thing = Thing(UUID.fromString(obj.getString("uuid")))
thing.summary = obj.getString("summary")
things += thing
println(thing)
}
adapter?.notifyDataSetChanged()
findViewById<ProgressBar>(R.id.ac_main_progress).visibility = View.GONE
}
private fun fillRecyclerView(things : List<Thing>) {
val recyclerView = findViewById<RecyclerView>(R.id.main_recycler)
val mainActivityRecyclerAdapter = MainActivityAdapter(this, things)
adapter = mainActivityRecyclerAdapter
recyclerView.adapter = mainActivityRecyclerAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
}
}
```
you can use fuel-rxjava (https://github.com/kittinunf/fuel/tree/master/fuel-rxjava) for showing progress. Register an observer to notify the progress bar using rx-java

How to copy a property between 2 lists of different types using declarative Kotlin?

Context
Using a declarative approach in Kotlin, need to copy a single name property from List of User objects to a List of UserDetail objects based on matching id properties as shown below:
val users = Arrays.asList(
User(1, "a"),
User(2, "b")
)
val details = Arrays.asList(
UserDetail(1),
UserDetail(2)
)
val detailsWithName = copyNameToUser(users, details)
Models are:
class User {
var id = -1;
var name = "" // given for all Users
constructor(id: Int, name: String)
// ...
}
class UserDetail {
var id = -1;
var name = "" // blank for all UserDetails
constructor(id: Int)
// ...
}
Problem
Tried to use a declarative approach via forEach iterable function:
fun copyNameToDetails(users: List<User>, details: List<UserDetail>): List<UserDetail> {
details.forEach(d ->
users.forEach(u ->
if (d.id == u.id) {
d.name = u.name
}
)
)
return details
}
This can be achieved in Java as shown below:
private static List<UserDetail> copyNameToDetails(List<User> users, List<UserDetail> details) {
for (UserDetail d: details) {
for (User u : users) {
if (d.id == u.id) {
d.name = u.name;
}
}
}
return details;
}
Question
How can this be done in Kotlin using a declarative approach?
You make too many iterations over both lists (users.size * details.size) so creating a hashmap can fix it a bit:
fun copyNameToUsers(users: List<User>, details: List<UserDetail>): List<UserDetail> {
val usersById = users.associate { it.id to it }
details.forEach { d ->
usersById[d.id]?.let { d.name = it.name }
}
return details
}
An other approach with non mutable values :
data class User(val id: Int = -1, val name: String = "")
data class UserDetail(val id: Int = -1, val name: String = "")
private fun List<UserDetail>.copyNameToUser(users: List<User>): List<UserDetail> = map { userDetail ->
users.firstOrNull { userDetail.id == it.id }?.let { userDetail.copy(name = it.name) } ?: userDetail
}

Create method with generic parameter for repeated actions

I have the following methods on my code:
fun saveArticles(data: JSONArray) {
var active = String()
for (i in 0..data.length().minus(1)) // create string
val articles = Select.from(Article::class.java).list()
val iterator = articles.iterator()
while (iterator.hasNext()) {
val article = iterator.next() as Article
if (!active.contains(Regex(article.id.toString()))) article.delete()
}
}
fun saveDossiers(data: JSONArray) {
var active = String()
for (i in 0..data.length().minus(1)) // create string
val dossiers = Select.from(Dossier::class.java).list()
val iterator = dossiers.iterator()
while (iterator.hasNext()) {
val dossier = iterator.next() as Dossier
if (!active.contains(Regex(dossier.id.toString()))) dossier.delete()
}
}
fun saveVideos(data: JSONArray) {
var active = String()
for (i in 0..data.length().minus(1)) // create string
val videos = Select.from(Video::class.java).list()
val iterator = videos.iterator()
while (iterator.hasNext()) {
val video = iterator.next() as Video
if (!active.contains(Regex(video.id.toString()))) video.delete()
}
}
As you can see, all the methods do exactly the same thing. The only difference is the Class type of the object I'm working at the moment. Can I somehow create a single method with a parameter of Class type, and depending of the type change the class I need to work? Something like this:
fun saveVideos(data: JSONArray, type: Class) {
var active = String()
for (i in 0..data.length().minus(1)) // create string
val list = Select.from(type).list()
val iterator = list.iterator()
while (iterator.hasNext()) {
val item = iterator.next() as type
if (!active.contains(Regex((item as type).id.toString()))) item.delete()
}
}
You need to extract an interface and use a reified generic.
interface Blabla {
fun delete()
val id: Int
}
inline fun <reified T : Blabla>saveVideos(data: JSONArray) {
var active = String()
for (i in 0..data.length().minus(1)) // create string
val list = Select.from(T::class.java).list()
val iterator = list.iterator()
while (iterator.hasNext()) {
val item = iterator.next() as T
if (Regex(item.id.toString()) !in active) item.delete()
}
}
This should work.
Also, I highly recommend you to use the Kotlin collection library, like this.
inline fun <reified T : Blabla>saveVideos(data: JSONArray) {
val active = ""
for (i in 0 until data.length()) {} // create string
val list = Select.from(T::class.java).list()
list
.map { it as T }
.filter { Regex(it.id.toString()) !in active }
.forEach { it.delete() }
}
And you can even replace forEach { it.delete() } with forEach(T::delete)

Categories

Resources