Retrofit API call returns null - android

Here is the API/JSON data I am trying to use: https://pogoapi.net/api/v1/pokemon_names.json
The problem is that when I try and parse the JSON response null is only returned.
Models:
data class ListOfReleasedPokemon(
val releasedPokemon: List<PokemonResponse>
)
data class PokemonResponse(
val pokemonMap: Map<String, ReleasedPokemonModel>
)
data class ReleasedPokemonModel(
val id: Int,
val name: String
)
API call:
interface PogoApi {
#GET("released_pokemon.json")
suspend fun getReleasedPokemon(): ListOfReleasedPokemon
}
App Module:
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Singleton
#Provides
fun providePokemonRepository(
pogoApi: PogoApi
) = PokemonRepository(pogoApi)
#Singleton
#Provides
fun providePogoApi(): PogoApi{
// val gson = GsonBuilder()
// .registerTypeAdapter(ReleasedPokemonModel::class.java, JsonDeserializer())
// .create()
return Retrofit.Builder()
.baseUrl(POGO_API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(PogoApi::class.java)
}
}
Repository(Where the error is found):
#ActivityScoped
class PokemonRepository #Inject constructor(
private val pogoApi: PogoApi
){
suspend fun getReleasedPokemonList(): Resource<ListOfReleasedPokemon> {
val response = try{
pogoApi.getReleasedPokemon()
// Returns null
} catch(e: IOException){
return Resource.Error("Please check your internet connection")
} catch(e: HttpException){
return Resource.Error("Unexpected response")
}
Log.d("MainActivity", "GetPokemonReleasedList Run with no errors")
return Resource.Success(data = response)
}
}
Any help would be greatly appreciated!

Oh, I just noticed now that you seem to have a question related to this Json parser.
Well in this comment I will guide you in detail with this project of yours.
Because the previous question you asked only how to parse Json with undefined key.
First, create a custom Json Deserializer. Because here your data api has an undefined key. Here I will create 1 gson with custom Json Deserializer. As follows:
PokemonResponse
data class PokemonResponse(
val pokemonMap: List<StringReleasedPokemonModel>
)
data class ReleasedPokemonModel(
val id: Int,
val name: String
)
GsonHelper.kt
object GsonHelper {
fun create(): Gson = GsonBuilder().apply {
registerTypeAdapter(PokemonResponse::class.java, PokemonType())
setLenient()
}.create()
private class PokemonType : JsonDeserializer<PokemonResponse> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): PokemonResponse {
val list = mutableListOf<ReleasedPokemonModel>()
// Get your all key
val keys = json?.asJsonObject?.keySet()
keys?.forEach { key ->
// Get your item with key
val item = Gson().fromJson<ReleasedPokemonModel>(
json.asJsonObject[key],
object : TypeToken<ReleasedPokemonModel>() {}.type
)
list.add(item)
}
return PokemonResponse(list)
}
}
}
Next, Provide this Gson to your Dagger or Hilt.
AppModule.kt
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Provides
fun provideGson(): Gson = GsonHelper.create()
...
}
And then you modify your addConverterFactory as follows:
AppModule.kt
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Provides
fun provideGson(): Gson = GsonHelper.create()
#Singleton
#Provides
fun providePogoApi(gson: Gson): PogoApi = Retrofit.Builder()
.baseUrl(POGO_API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
.create(PogoApi::class.java)
}
In PogoApi, change the return type of getReleasedPokemon() to PokemonResponse.
interface PogoApi {
#GET("released_pokemon.json")
suspend fun getReleasedPokemon(): PokemonResponse
}
And finally, in your repository, edit the following:
PokemonRepository.kt
#ActivityScoped
class PokemonRepository #Inject constructor(
private val pogoApi: PogoApi
){
suspend fun getReleasedPokemonList(): Resource<PokemonResponse> = try {
val response = pogoApi.getReleasedPokemon()
Log.d("MainActivity", "list: ${response}")
Resource.Success(data = response)
} catch(e: IOException){
Resource.Error("Please check your internet connection")
} catch(e: HttpException){
Resource.Error("Unexpected response")
}
}

Related

Room TypeConverter with constructor error: java.lang.IllegalArgumentException

I have been struggling with this error for a long time. I have seen similar topics while no answer has brought the result. Following this tutorial https://www.section.io/engineering-education/storing-custom-data-types-with-custom-typeconverter-in-room-database/.
Error: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.cointracker/com.example.cointracker.MainActivity}: java.lang.IllegalArgumentException: A required type converter (class com.example.cointracker.data.util.converters.Converters) for com.example.cointracker.data.db.CoinDAO is missing in the database configuration.
My TypeConverter is:
#ProvidedTypeConverter
class Converters(
private val jsonParser: JsonParser
) {
#TypeConverter
fun toQuoteJson(quote: Quote): String{
return jsonParser.toJson(
quote,
object : TypeToken<Quote>(){}.type
) ?: "[]"
}
#TypeConverter
fun fromQuoteJson(json: String): Quote{
return jsonParser.fromJson<Quote>(
json,
object: TypeToken<Quote>(){}.type
) ?: Quote(null)
}
#TypeConverter
fun toStringJson(meaning: List<String?>?) : String {
return jsonParser.toJson(
meaning,
object : TypeToken<List<String>>(){}.type
) ?: "[]"
}
#TypeConverter
fun fromStringsJson(json: String): List<String>{
return jsonParser.fromJson<List<String>>(
json,
object: TypeToken<List<String>>(){}.type
) ?: emptyList()
}
}
Class which is extending simple interface JsonParser:
class GsonParser(
private val gson: Gson
): JsonParser {
override fun <T> fromJson(json: String, type: Type): T? {
return gson.fromJson(json, type)
}
override fun <T> toJson(obj: T, type: Type): String? {
return gson.toJson(obj, type)
}
}
Database:
#Database(
entities = [Coin::class],
version = 2,
exportSchema = false
)
#TypeConverters(Converters::class)
abstract class CoinDatabase : RoomDatabase() {
abstract fun getCoinDAO(): CoinDAO
}
Dependencies with Hilt:
#Module
#InstallIn(SingletonComponent::class)
class JsonModule {
#Singleton
#Provides
fun provideGson(): Gson {
return GsonBuilder().create()
}
#Singleton
#Provides
fun provideJsonParser(gson: Gson): JsonParser {
return GsonParser(gson)
}
}
and for database:
#Module
#InstallIn(SingletonComponent::class)
class DatabaseModule {
#Singleton
#Provides
fun provideCoinsDatabase(app: Application): CoinDatabase {
return Room.databaseBuilder(app,CoinDatabase::class.java,"coins_db")
.addTypeConverter(Converters::class)
.build()
}
#Singleton
#Provides
fun provideCoinsDao(coinDatabase: CoinDatabase): CoinDAO{
return coinDatabase.getCoinDAO()
}
}
Does anyone know how to solve this error?
You should initialise & add the Converters class in the addTypeConverter method when building the Room database.
Don't simply pass the Converters::class.
It should be:
val converterFactory = Converters(jsonParserObject)
Room.databaseBuilder(app, CoinDatabase::class.java,"coins_db")
.addTypeConverter(converterFactory)
.build()
Source: this & this.

Retrofit APIService Injection by Dagger Hilt in MVP Architecture's Model Class

I am trying to inject retrofit APIServices dependency into the model class. Here is My API Module Source Code:
#Module
#InstallIn(SingletonComponent::class)
object ApiModule {
#Singleton
#Provides
fun providesHttpLoggingInterceptor() = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
#Singleton
#Provides
fun providesOkHttpClient(httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient =
OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.build()
#Singleton
#Provides
fun providesRetrofit(okHttpClient: OkHttpClient): Retrofit =
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(ApiConfig.BASE_URL)
.client(okHttpClient)
.build()
#Singleton
#Provides
#Named("ApiService")
fun providesApiService(retrofit: Retrofit):ApiServices =
retrofit.create(ApiServices::class.java)
}
For User Registration, I am using MVP Architecture Pattern where FragmentRegistration.kt is view layer, RegistrationModel is model layer class
When I inject ApiServices dependency into FragmentRegistration, it works fine. But when I try to inject it into model layer class, which is RegistrationModel, It doesn't work.
RegistrationModel:
class RegistrationModel(
val presenter: RegistrationContract.Presenter
) : RegistrationContract.Model {
#Inject
#Named("ApiService")
lateinit var apiServices: ApiServices
override fun onDataReady(registrationData: RegistrationData) {
val map = mapOf(
"Accept" to "application/json",
"Content-Type" to "application/json"
)
apiServices.userRegistration(map, registrationData)
.enqueue(object : Callback<RegistrationResponse> {
override fun onResponse(
call: Call<RegistrationResponse>,
response: Response<RegistrationResponse>
) {
if (response.isSuccessful) {
Log.d(TAG, "onDataReady: ${response.body().toString()}")
} else {
val apiFailure = APIFailure(
response.code(),
response.message()
)
presenter.onSignupFailure(apiFailure)
Log.d(TAG, "onDataReady: Error ${response.code()}")
Log.d(TAG, "onDataReady: Error Body ${response.errorBody()}")
}
}
override fun onFailure(call: Call<RegistrationResponse>, t: Throwable) {
presenter.onSignupFailure(
APIFailure(-1, t.toString())
)
Log.d(TAG, "onFailure: $t")
}
})
}
companion object {
const val TAG = "RegistrationModel"
}
}
In the above's Code,
#Inject
#Named("ApiService")
lateinit var apiServices: ApiServices
this dependency injection is not working.
You are trying to inject a filed provided by Hilt into a class which is not managed by Hilt. This will not work out of the box. You have to define EntryPoint for you custom class, so the Hilt can perform injection. You can read how to do that here: https://developer.android.com/training/dependency-injection/hilt-android#not-supported

Error when using Type converters with dagger hilt

I am new to dependency injection and currently using dagger-hilt. I had to use type converter in order to insert a list inside an entity.
At run time I am facing a crash because the converter is missing in the database configuration although I am adding it.
Below is my code regarding the type converter, databse class and App Module
#ProvidedTypeConverter
class BundleConverter {
#TypeConverter
fun fromPackageBundleList(countryLang: List<PackageBundle?>?): String? {
if (countryLang == null) {
return null
}
val gson = Gson()
val type: Type = object : TypeToken<List<PackageBundle?>?>() {}.type
return gson.toJson(countryLang, type)
}
#TypeConverter
fun toPackageBundleList(countryLangString: String?): List<PackageBundle>? {
if (countryLangString == null) {
return null
}
val gson = Gson()
val type: Type = object : TypeToken<List<PackageBundle?>?>() {}.type
return gson.fromJson<List<PackageBundle>>(countryLangString, type)
}
}
#Database(
entities = [Service::class,SoundEffect::class],
version = 3,
exportSchema = false
)
#TypeConverters(BundleConverter::class)
abstract class UserDatabase : RoomDatabase() {
abstract fun getYourDao(): UserDao
}
#Module
#InstallIn(SingletonComponent::class)
class AppModule {
#Singleton
#Provides
fun provideYourDatabase(
#ApplicationContext app: Context
) = Room.databaseBuilder(
app,
UserDatabase::class.java,
"your_db_name"
)
.addTypeConverter(BundleConverter::class)
.build()
#Singleton
#Provides
fun provideYourDao(db: UserDatabase) = db.getYourDao()
}
Try to remove optional value handling from your database converter class:
class BundleConverter {
#TypeConverter
fun fromPackageBundleList(countryLang: List<PackageBundle>): String =
return Gson().toJson(countryLang)
#TypeConverter
fun toPackageBundleList(countryLangString: String): List<PackageBundle> {
val type: Type = object : TypeToken<List<PackageBundle>>() {}.type
return Gson().fromJson(countryLangString, type)
}
}

Retrofit returns 200 response code but I'm receiving null when accessing fields

I'm using retrofit to make a network request to an API. The response code returns 200 but I am receiving null when trying to access the fields. I have checked out other solutions but can't seem to solve my problem. I am using hilt
Here is my API class
interface BlockIOApi{
#GET("/api/v2/get_balance/")
suspend fun getBalance(
#Query("api_key")
apiKey: String = BuildConfig.API_KEY
): Response<BalanceResponse>
}
and here is my app module object
AppModule
#Module
#InstallIn(ApplicationComponent::class)
object AppModule{
#Singleton
#Provides
fun provideOkHttpClient() = if (BuildConfig.DEBUG) {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
} else OkHttpClient
.Builder()
.build()
#Provides
#Singleton
fun providesRetrofit(okHttpClient: OkHttpClient): Retrofit =
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.client(okHttpClient)
.build()
#Provides
#Singleton
fun providesApiService(retrofit: Retrofit): BlockIOApi = retrofit.create(BlockIOApi::class.java)
}
And finally here is my repositories, DefaultRepository.kt
class DefaultRepository #Inject constructor(
private val blockIOApi: BlockIOApi,
private val balanceDao: BalanceDao
):BlockIORepository {
override suspend fun getBalance(): Resource<BalanceResponse> {
return try {
val response = blockIOApi.getBalance()
Log.d("TAG", "getBalance>>Response:${response.body()?.balance} ")
if (response.isSuccessful){
response.body().let {
return#let Resource.success(it)
}
}else{
Log.d("TAG", "getBalance: Error Response >>> ${response.message()}")
Resource.error("An unknown error occured",null)
}
}catch (ex :Exception){
Resource.error("Could not reach the server.Check your internet connection",null)
}
}
and this interface,BlockIORepository.kt
interface BlockIORepository {
suspend fun getBalance(): Resource<BalanceResponse>
suspend fun insertBalance(balance: Balance)
suspend fun getCachedBalance(): Balance
suspend fun getAddresses(): Resource<DataX>
}
Here are my data classes
data class BalanceResponse(
val balance: Balance,
val status: String
)
#Entity
data class Balance(
val available_balance: String,
val network: String,
val pending_received_balance: String,
#PrimaryKey(autoGenerate = false)
var id: Int? = null
)
The problem comes when I try to access the data object. I am not getting null for the status object
I have been stuck on this for two days now. Any help will be highly appreciated. Thanks in advance.
The problem is occured here:
data class BalanceResponse(
val balance: Balance, <-- in postman it is "data"
val status: String
)
You should consider putting #SerializedName(xxx) for your class.
data class BalanceResponse(
#SerializedName("data") val balance: Balance,
val status: String
)
Your class should name filed as per the json or it should provide #SerializedName
So your BalanceResponse class should be
data class BalanceResponse(
#SerializedName("data")
val balance: Balance,
#SerializedName("status")
val status: String
)
Since you are trying to hold data in balance, you must provide SerializedName, but if they have the same name and with exact-case then the parser will automatically recognize them.

How to Inject Moshi/Gson in Room TypeConvertors using Hilt?

I am trying out hilt and i want to inject moshi for serializing and deserializing.
Here's a code sample from a github Repo which is not using di:
open class InfoTypeConverter {
private val moshi = Moshi.Builder().build() //not using dependency injection
#TypeConverter
fun fromString(value: String): PokemonInfo.Type? {
val adapter: JsonAdapter<PokemonInfo.Type> = moshi.adapter(PokemonInfo.Type::class.java)
return adapter.fromJson(value)
}
#TypeConverter
fun fromInfoType(type: PokemonInfo.Type): String {
val adapter: JsonAdapter<PokemonInfo.Type> = moshi.adapter(PokemonInfo.Type::class.java)
return adapter.toJson(type)
}
}
I am Trying out random stuff to field inject this like annotaion with #AndroidEntryPoint/#EntryPoint and obviously it's not working.
Including Moshi in the Hilt dependency graph is as simple as adding this class:
#Module
#InstallIn(ApplicationComponent::class)
object DataModule {
#Singleton
#Provides
fun provideMoshi(): Moshi {
return Moshi.Builder().build()
}
}
#TypeConverters are for the Room database. If you want to use the Moshi from Hilt for them, you will have to try a bit. One way of doing this is:
Put #TypeConverter functions in a static context (object declaration) with an initializer
object InfoTypeConverter {
private lateinit var moshi: Moshi
fun initialize(moshi: Moshi){
this.moshi = moshi
}
#TypeConverter
fun fromString(value: String): PokemonInfo.Type? {
val adapter: JsonAdapter<PokemonInfo.Type> = moshi.adapter(PokemonInfo.Type::class.java)
return adapter.fromJson(value)
}
#TypeConverter
fun fromInfoType(type: PokemonInfo.Type): String {
val adapter: JsonAdapter<PokemonInfo.Type> = moshi.adapter(PokemonInfo.Type::class.java)
return adapter.toJson(type)
}
}
Initialize InfoTypeConverter before creating your RoomDatabase (here using the same module for the purpose):
#Module
#InstallIn(ApplicationComponent::class)
object DataModule {
#Singleton
#Provides
fun provideMoshi(): Moshi {
return Moshi.Builder().build()
}
#Singleton
#Provides
fun provideRoomDatabase(moshi: Moshi): YourDatabase {
InfoTypeConverter.initialize(moshi)
val yourDatabase: YourDatabase = /* create your room database here */
return yourDatabase
}
}

Categories

Resources