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