Attempt to invoke interface method 'int java.util.List.size()' on a null object reference on passing Parcelable in Intent Kotlin - kotlin

So I have a sample app that is currently using Retrofit to fetch data and display it in a recyclerview with its custom adapter. I want to pass the data to the a more details page when I click on a character name on my recyclerView. I found some tutorials and decided to use the Parcelize annotation like this:
#Parcelize data class Character (
val charID: Long,
val name: String,
val birthday: String,
val occupation: List<String>,
val img: String,
val status: String,
val nickname: String,
val appearance: List<Long>,
val portrayed: String,
val category: String,
val betterCallSaulAppearance: List<Long> ) : Parcelable
My adapter looks like this:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val character = characters[position]
holder.characterNameText.text = character.name
holder.characterNameText.setOnClickListener {
holder.passData(character)
}
}
override fun getItemCount() = characters.size
class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView) {
val characterNameText = itemView.findViewById<TextView>(R.id.character_name)
fun passData(character : Character) {
val intent = Intent(itemView.context, CharacterDetails::class.java)
intent.putExtra(CHARACTER, character)
itemView.context.startActivity(intent)
}
}
And in the CharacterDetails Activity it looks like this:
companion object {
const val CHARACTER = "character"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_character_details)
val bundle = intent.extras
val character = bundle!!.getParcelable<Character>(CHARACTER)
val characterName = findViewById<TextView>(R.id.character_details_name)
Yet I get a
java.lang.NullPointerException: Attempt to invoke interface method 'int java.util.List.size()' on a null object reference
at com.example.myapplication.models.Character.writeToParcel(Unknown Source:85)
I'm still new to this so I really need your help.Thanks!

Obviously, the error caused by the list reference of the data class is null. You can modify the code like this.
#Parcelize data class Character (
val charID: Long,
val name: String,
val birthday: String,
val occupation: List<String>?,
val img: String,
val status: String,
val nickname: String,
val appearance: List<Long>?,
val portrayed: String,
val category: String,
val betterCallSaulAppearance: List<Long>? ) : Parcelable

Related

how can I convert my room entity to my data class in kotlin?

I have a data class that I pull from internet and I want to save room database but there is a problem like that.
It always gives an error like this, how can I overcome this problem?
my room entity class
#Entity(tableName = "ExchangeValues")
data class ExchangeEntity(
#ColumnInfo(name = "base_code") val base_code: String,
#ColumnInfo(name = "conversion_rates") val conversion_rates: ConversionRates,
#ColumnInfo(name = "result") val result: String,
#PrimaryKey(autoGenerate = true) val uid:Int?=null
)
my dao
#Dao
interface ExchangeDao {
#Query("SELECT * FROM ExchangeValues")
suspend fun getAll() : List<ExchangeEntity>
#Query("UPDATE ExchangeValues SET base_code=:base_code,conversion_rates=:conversion_rates , result=:result")
suspend fun update(base_code:String,conversion_rates:ConversionRates,result:String)
}
my exchange data class
#Serializable
data class Exchange(
val base_code: String,
val conversion_rates: ConversionRates,
val documentation: String,
val result: String,
val terms_of_use: String,
val time_last_update_unix: Int,
val time_last_update_utc: String,
val time_next_update_unix: Int,
val time_next_update_utc: String
) {
fun toEntity() = ExchangeEntity(
base_code = base_code,
conversion_rates = conversion_rates,
result = result
)
}
#Serializable
data class ConversionRates(
val conversionRates : Map<String,Double>
)
I cant use toEntity function in getAll()
exchangeRepositoryImpl
class ExchangeRepositoryImpl #Inject constructor(
private val dao:ExchangeDao
) : ExchangeRepository{
override suspend fun getAll() : Flow<List<Exchange>> {
return flow {
emit(dao.getAll())
}
}
override suspend fun update(exchange: Exchange) {
dao.update(exchange.base_code,exchange.result,exchange.conversion_rates)
}
}
my exchange converter
class ExchangeConverter {
#TypeConverter
fun fromSource(conversionRates: ConversionRates) : String{
val gson = Gson()
return gson.toJson(conversionRates)
}
#TypeConverter
fun toSource(json: String): ConversionRates {
val gson = Gson()
val typeToken = object : TypeToken<List<ConversionRates>>() {}.type
return Gson().fromJson(json, typeToken)
}
}
I wrote a converter like this, but it might not be correct, I'm not so sure. How can I solve this problem?
Inside flow you have created call map function the call to toEntity() eg
flow{
emit (dao.getAll().map{it.toEntity()})
}
Well your flow returns a flow of
List<Exchange>
and your repo returns
List<ExchangeEntity>
and there's nothing in your code to map an ExchangeEntity to an Exchange.
So you need something like:
override suspend fun getAll() : Flow<List<Exchange>> {
return flow {
emit(dao.getAll().map{Exchange(base_code = it.baseCode)})// add in other fields on exchange constructor
}
}

Why retrofit returns null in response body?

Get null response results in response body. Using retrofit, dagger and repository. Why get null in response I don't know. My model seem right. Whats the problem?
MainViewModel.kt
#HiltViewModel
class MainViewModel#Inject constructor(
private val repository: MovieRepository,
#ApplicationContext private val context: Context
) : ViewModel() {
val movieList = MutableLiveData<Resource<Movie>>()
fun getAllMovies(movieName: String) {
movieList.postValue(Resource.Loading())
viewModelScope.launch {
try {
if (hasInternetConnection(context)) {
val response = repository.getMovies(movieName, "ffe9123f")
movieList.postValue(Resource.Success(response.body()!!))
} else
movieList.postValue(Resource.Error("Internet yok"))
} catch (ex: Exception) {
when (ex) {
is IOException -> movieList.postValue(Resource.Error("Network Failure " + ex.localizedMessage))
else -> movieList.postValue(Resource.Error("Conversion Error"))
}
}
}
}
}
Resource.kt
sealed class Resource<T>(
val data: T? = null,
val message: String? = null
) {
class Success<T>(data: T): Resource<T>(data)
class Error<T>(message: String, data: T? = null): Resource<T>(data, message)
class Loading<T> : Resource<T>()
}
MovieRepository.kt
#Singleton
class MovieRepository #Inject constructor(private val movieAppService: MovieAppService) {
suspend fun getMovies(title: String, aKey: String): Response<Movie> = withContext(
Dispatchers.IO
) {
val movies = movieAppService.getMovies(title = title, aKey = aKey)
movies
}
Movie.kt
data class Movie(
val title: String,
val year: String,
val rated: String,
val released: String,
val runtime: String,
val genre: String,
val director: String,
val writer: String,
val actors: String,
val plot: String,
val language: String,
val country: String,
val awards: String,
val poster: String,
val ratings: List<Rating>,
val metascore: String,
val imdbRating: String,
val imdbVotes: String,
val imdbID: String,
val type: String,
val dvd: String,
val boxOffice: String,
val production: String,
val website: String,
val response: String
)
MovieAppService.kt
interface MovieAppService {
companion object {
const val ENDPOINT = "http://www.omdbapi.com/"
}
#GET(".")
suspend fun getMovies(#Query("t") title: String,#Query("apikey") aKey: String): Response<Movie>
}
{"Title":"A Beautiful Mind","Year":"2001","Rated":"PG-13","Released":"04 Jan 2002","Runtime":"135 min","Genre":"Biography, Drama","Director":"Ron Howard","Writer":"Akiva Goldsman, Sylvia Nasar","Actors":"Russell Crowe, Ed Harris, Jennifer Connelly","Plot":"After John Nash, a brilliant but asocial mathematician, accepts secret work in cryptography, his life takes a turn for the nightmarish.","Language":"English","Country":"United States","Awards":"Won 4 Oscars. 37 wins & 69 nominations total","Poster":"https://m.media-amazon.com/images/M/MV5BMzcwYWFkYzktZjAzNC00OGY1LWI4YTgtNzc5MzVjMDVmNjY0XkEyXkFqcGdeQXVyMTQxNzMzNDI#._V1_SX300.jpg","Ratings":[{"Source":"Internet Movie Database","Value":"8.2/10"},{"Source":"Rotten Tomatoes","Value":"74%"},{"Source":"Metacritic","Value":"72/100"}],"Metascore":"72","imdbRating":"8.2","imdbVotes":"908,920","imdbID":"tt0268978","Type":"movie","DVD":"25 Jun 2002","BoxOffice":"$170,742,341","Production":"N/A","Website":"N/A","Response":"True"}
Movie model is wrong. I didn't realize the starting letter is uppercase letter.
Movie.kt
data class Movie(
val Title: String,
val Year: String,
val Rated: String,
val Released: String,
val Runtime: String,
val Genre: String,
val Director: String,
val Writer: String,
val Actors: String,
val Plot: String,
val Language: String,
val Country: String,
val Awards: String,
val Poster: String,
val Ratings: List<Rating>,
val Metascore: String,
val imdbRating: String,
val imdbVotes: String,
val imdbID: String,
val Type: String,
val Dvd: String,
val boxOffice: String,
val Production: String,
val Website: String,
val Response: String
)

Entity classes have some issues while using retrofit

I am getting this error while getting a response from an API.
Sometimes, it works fine but 8/10 times it crashes and shows me this error
java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.hashCode()' on a null object reference
at com.example.newsapi.db.Entities.Source.hashCode(Unknown Source:2)
at com.example.newsapi.db.Entities.Article.hashCode(Unknown Source:50)
This is the Article entity
#Entity(tableName = "Article_Table")
data class Article(
#PrimaryKey(autoGenerate = true)
val id: Int?=null,
val author: String,
val content: String,
val description: String,
val publishedAt: String,
val source: Source,
val title: String,
val url: String,
val urlToImage: String
): Serializable
This is the Source class declared in the Article class
data class Source(
val id: String,
val name: String
)
Here is where I'm trying to put that Article object inside a Bundle
setOnClickListener {
val bundle = Bundle().apply {
putSerializable("article", currentArticle)
}
when (from) {
"BreakingNewsFragment" -> findNavController().navigate(R.id.action_breakingNewsFragment_to_articleFragment, bundle)
"SearchNewsFragment" -> findNavController().navigate(R.id.action_searchNewsFragment_to_articleFragment, bundle)
"SavedNewsFragment" -> findNavController().navigate(R.id.action_savedNewsFragment_to_articleFragment, bundle)
}
}
Here is the API call function
#GET("v2/top-headlines")
suspend fun getBreakingNews(
#Query("country")
countryCode: String = "us",
#Query("page")
pageNumber : Int = 1,
#Query("apiKey")
apiKey: String = API_KEY
): Response<NewsResponse>
calling that function from the repository
suspend fun getBreakingNews(countryCode: String, pageNumber: Int): Response<NewsResponse> {
return RetrofitInstance.api.getBreakingNews(
countryCode = countryCode,
pageNumber = pageNumber
)
}
calling that repository function from the viewmodel
fun getBreakingNews(countryCode: String){
viewModelScope.launch(Dispatchers.IO) {
breakingNews.postValue(Resource.Loading())
val response = articleRepository.getBreakingNews(countryCode, breakingNewsPage)
breakingNews.postValue(handleBreakingNewsResponse(response))
}
}
Here is the NewsResponse Class
data class NewsResponse(
val articles: List<Article>,
val status: String,
val totalResults: Int
)

mutableListOf to Bundle (Kotlin)

I have a mutableLIst:
var books = mutableListOf<Book>()
model "Book" is:
data class Book(val title: String, val id: Int)
My code is:
button2.setOnClickListener{
val delFragment = DelFragment()
val booksforDel = Bundle()
booksforDel.putStringArrayList("books", books as ArrayList<String>)
delFragment.setArguments(booksforDel)
val manager = supportFragmentManager
delFragment.show(manager,"Delete Book")
}
in Fragment I try to get data:
val booksForDelete = getArguments()?.getStringArrayList("books")!!
And get Error:
java.lang.ArrayStoreException: source[0] of type com.example.http_example.model.Book cannot be stored in destination array of type java.lang.String[]
How send a data from mutableList "books" to Bundle in DialogFragment?
You can implement Parcelable interface:
data class Book(val title: String, val id: Int) : Parcelable {
constructor(source: Parcel) : this(
source.readString()!!,
source.readInt()
)
override fun describeContents() = 0
override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) {
writeString(title)
writeInt(id)
}
companion object {
#JvmField
val CREATOR: Parcelable.Creator<Book> = object : Parcelable.Creator<Book> {
override fun createFromParcel(source: Parcel): Book = Book(source)
override fun newArray(size: Int): Array<Book?> = arrayOfNulls(size)
}
}
}
And use it like the following:
var books = mutableListOf<Book>()
val booksforDel = Bundle()
booksforDel.putParcelableArray("books", books.toTypedArray())
Ann to retrieve books in a Fragment:
val booksForDelete = arguments?.getParcelableArray("books")

Best practice for handling null types in classes when mapping classes together

I am using Kotlin 1.30. I have the following entity classes that will be populated from the API. And some of the properties could be null from the API so I have declared them using the safe null type.
However, I will map this entity class to my domain model class. And just wondering what is the best way to handle the null types?
I have 2 ideas on how to do this when I map the classes.
declare all the equivalent properties as safe null types
use the elivs operator to return either a empty string or a emptylist
In the following snippet I am using the elvis operator. Just wondering what is the best practice for this?
class LoginResponseDomainMapperImp : LoginResponseDomainMapper {
override fun map(entity: LoginResponseEntity): LoginResponse {
return LoginResponse(
entity.token ?: "",
mapUsers(entity.user),
mapEnterprises(entity.enterprises ?: emptyList()),
mapVendors(entity.vendors ?: emptyList()))
}
private fun mapUsers(userEntity: UserEntity?): User {
return User(
userEntity?.id,
userEntity?.email,
userEntity?.firstName,
userEntity?.lastName,
userEntity?.phone,
userEntity?.address,
userEntity?.dob,
userEntity?.customer,
userEntity?.enterpriseIds ?: emptyList(),
userEntity?.vendorIds ?: emptyList())
}
private fun mapEnterprises(enterprisesEntity: List<EnterprisesEntity>): List<Enterprises> {
val enterpriseList = mutableListOf<Enterprises>()
enterprisesEntity.forEach {
val enterprise = Enterprises(
it.id,
it.enterpriseName,
it.typeLabel,
it.country,
it.imageId,
it.managers,
it.members,
it.stripe,
it.locations)
enterpriseList.add(enterprise)
}
return enterpriseList.toList()
}
private fun mapVendors(vendorsEntity: List<VendorsEntity>): List<Vendors> {
val vendorList = mutableListOf<Vendors>()
vendorsEntity.forEach {
val vendor = Vendors(
it.id,
it.vendorName,
it.typeLabel,
it.userRole,
it.latitude,
it.longitude,
it.partner,
it.country,
it.imageId,
it.stripe)
vendorList.add(vendor)
}
return vendorList.toList()
}
}
Entity class that will populate from the API, so any of these could be null
data class LoginResponseEntity(
#SerializedName("token") val token: String?,
#SerializedName("user") val user: UserEntity?,
#SerializedName("enterprises") val enterprises: List<EnterprisesEntity>?,
#SerializedName("vendors") val vendors: List<VendorsEntity>?)
data class UserEntity(
#SerializedName("id") val id: String?,
#SerializedName("email") val email: String?,
#SerializedName("first_name") val firstName: String?,
#SerializedName("last_name") val lastName: String?,
#SerializedName("phone") val phone: String?,
#SerializedName("address") val address: String?,
#SerializedName("dob") val dob: String?,
#SerializedName("customer") val customer: String?,
#SerializedName("enterprise_ids") val enterpriseIds: List<String>?,
#SerializedName("vendor_ids") val vendorIds: List<String>?)
data class EnterprisesEntity(
#SerializedName("id") val id: String?,
#SerializedName("enterprise_name") val enterpriseName: String?,
#SerializedName("type_label") val typeLabel: String?,
#SerializedName("referral_code") val referralCode: String?,
#SerializedName("country") val country: String?,
#SerializedName("image_id") val imageId: String?,
#SerializedName("managers") val managers: List<String>?,
#SerializedName("members") val members: List<String>?,
#SerializedName("stripe") val stripe: Boolean,
#SerializedName("locations") val locations: List<String>?)
data class VendorsEntity(
#SerializedName("id") val id: String?,
#SerializedName("vendor_name") val vendorName: String?,
#SerializedName("type_label") val typeLabel: String?,
#SerializedName("user_role") val userRole: String?,
#SerializedName("latitude") val latitude: Float,
#SerializedName("longitude") val longitude: Float,
#SerializedName("partner") val partner: Boolean,
#SerializedName("country") val country: String?,
#SerializedName("image_id") val imageId: String?,
#SerializedName("stripe") val stripe: Boolean)
Data model class in the domain, Its it better to declare them all safe null types?
data class LoginResponse(
val token: String,
val user: User?,
val enterprises: List<Enterprises>,
val vendors: List<Vendors>)
data class User(
val id: String?,
val email: String?,
val firstName: String?,
val lastName: String?,
val phone: String?,
val address: String?,
val dob: String?,
val customer: String?,
val enterpriseIds: List<String>,
val vendorIds: List<String>)
data class Enterprises(
val id: String,
val enterpriseName: String,
val typeLabel: String,
val country: String,
val imageId: String,
val managers: List<String>,
val members: List<String>,
val stripe: Boolean,
val locations: List<String>)
data class Vendors(
val id: String,
val vendorName: String,
val typeLabel: String?,
val userRole: String,
val latitude: Float,
val longitude: Float,
val partner: Boolean,
val country: String?,
val imageId: String,
val stripe: Boolean)
First of all there are no safe null types in Kotlin. A variable is either nullable or not.
If the API delivers null values, either by absence or by setting them explicitely null, your data classes should reflect that in the way that those variables are nullable (question mark ? right after the data type).
The moment you map those data classes to your entities (domain model) you should handle the null case properly. Since it is a valid case, that the API serves null values, which you expect, you should handle that case by assigning a default value.
Using the elvis operator is just a way to deal with nullable types elegantely, but if you use it, remains your choice.
If you declare all the properties as nullable that's not much better than Java, even when you access them with the null safety operator. If your JSON properties have null value, that means that in your business logic they don't always have a value, and is your responsibility to handle that, by fallback to a default value (i.e. emptyList() or ""), or maybe something more complicated like redirecting to a sign in screen.