Android Kotlin Dagger2 provide gson : Parameter specified as non-null is null - android

i want to provide a single gson instance, so i provide it in my NetworkModule, when i create retrofit api, gson is non null, but when i use gson instance in retrofit class, it is null..
#Module
class NetworkModule {
#Provides
#Singleton
fun provideGson(): Gson {
return Gson()
}
#Provides
#Singleton
fun provideMyEnterpriseApi(gson: Gson): MyEnterpriseApi {
//HERE GSON IS NOT NULL
return RetrofitMyEnterpriseApi(BuildConfig.API_MYENTERPRISE_URL,
BuildConfig.API_MYENTERPRISE_CONNECTION_TIMEOUT,
BuildConfig.API_MYENTERPRISE_READ_TIMEOUT,
gson)
}
}
My retrofit api class :
class RetrofitMyEnterpriseApi(baseUrl: String, connectTimeout: Long, readTimeout: Long, private val gson: Gson)
: RetrofitApi<RetrofitMyEnterpriseCalls>(baseUrl, connectTimeout, readTimeout, RetrofitMyEnterpriseCalls::class.java) {
override fun buildRetrofit(baseUrl: String, connectTimeout: Long, readTimeout: Long): Retrofit {
val builder = Retrofit.Builder()
builder.baseUrl(baseUrl)
builder.client(buildClient(connectTimeout, readTimeout))
builder.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
builder.addConverterFactory(GsonConverterFactory.create(gson))
return builder.build()
}
private fun buildClient(connectTimeout: Long, readTimeout: Long): OkHttpClient {
//HERE GSON IS NULL
return OkHttpClient.Builder()
.addInterceptor(OAuth2Interceptor(gson, this))
.addInterceptor(LoggingInterceptor.newInstance())
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
.readTimeout(readTimeout, TimeUnit.MILLISECONDS)
.build()
}
}
RetrofitApi class:
abstract class RetrofitApi<C>
(baseUrl: String, connectTimeout: Long, readTimeout: Long, callsClass: Class<C>) {
protected val calls: C
init {
val retrofit = buildRetrofit(baseUrl, connectTimeout, readTimeout)
calls = retrofit.create(callsClass)
}
protected abstract fun buildRetrofit(baseUrl: String, connectTimeout: Long, readTimeout: Long): Retrofit
}
So, when i provide it's not null, but when i use it in buildClient method is null, i can't see why, i miss something in Kotlin maybe.

I found answer when i posted \o/
So the problem was gson field in RetrofitMyEnterpriseApi class is instancied after the init of RetrofitApi parent class, i think it's strange because i pass it in parameters but..
I just need to call my buildRetrofit method after init of RetrofitMyEnterpriseApi class, or send gson instance to my parent class.

Related

Retrofit + Gson. Gson not calling custom deserializer

class GetBusinessMapResponseDTODeserializer : JsonDeserializer<GetBusinessMapResponseDTO> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): GetBusinessMapResponseDTO {
...
}
}
#Provides
#Singleton
fun provideRetrofit() : Retrofit {
val gsonBuilder = GsonBuilder()
.registerTypeAdapter(
GetBusinessMapResponseDTODeserializer::class.java,
GetBusinessMapResponseDTODeserializer())
.create()
val retrofit = Retrofit.Builder()
.baseUrl(IamHereBackendAPI.BASE_URL)
.addCallAdapterFactory(ResultAdapterFactory())
.addConverterFactory(GsonConverterFactory.create(gsonBuilder))
.build()
return retrofit
}
#POST("getAllBusinessesByFilterMap")
suspend fun getBusinessMapByFilter(
#Body getBusinessMapRequest: GetBusinessMapRequest
) : RequestResult<GetBusinessMapResponseDTO>
Hello! Can you please tell me why Gson does not want to use a custom deserializer? I thought the problem was that the response from the api was wrapped in the RequestResult class, but apparently this is not the problem
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
The problem is with your registerTypeAdapter call. The first argument is the class you want to register the adapter for. So instead of providing GetBusinessMapResponseDTODeserializer::class.java you should provide GetBusinessMapResponseDTO::class.java as argument:
val gsonBuilder = GsonBuilder()
.registerTypeAdapter(
GetBusinessMapResponseDTO::class.java,
GetBusinessMapResponseDTODeserializer())
.create()

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 API call returns null

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

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