Proper way to use SharedPreferences with retrofit singleton - android

I am using a PreferenceScreen to set a auth key and a url which I want to use in my retrofit API service.
So to get the auth key I need to access SharedPreferences inside my API service. But to do so I need a context. How can I pass context to my retrofit instance?
Here is my API service:
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(???)
private val BASE_URL = sharedPreferences.getString("api_url","")
private val TTN_KEY = sharedPreferences.getString("access_key","")
private val loggingInterceptor: HttpLoggingInterceptor =
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor { chain ->
val originalRequest = chain.request()
val newRequest = originalRequest.newBuilder()
.addHeader("Authorization", "key $TTN_KEY")
.build()
chain.proceed(newRequest)
}
.addInterceptor(loggingInterceptor)
.build()
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.client(okHttpClient)
.build()
interface TTNApiService {
#GET("devices")
suspend fun getDevices(): List<String>
#GET("query/{device-id}")
suspend fun getDeviceValues(#Path("device-id") id: String): List<NetworkValue>
#GET("query")
suspend fun getValues(): List<NetworkValue>
}
// public object used to access the retrofit instance
object TTNApi {
val retrofitService: TTNApiService by lazy {
retrofit.create(TTNApiService::class.java)
}
}

A basic solution is to store application context in a class which extends Application class. Like this
class MyApp : Application() {
override fun onCreate() {
instance = this
super.onCreate()
}
companion object {
var instance: MyApp? = null
private set
val context: Context?
get() = instance
}
}
Then you can get the context in your file like this
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(MyApp.context)
Also a preferred and proper way would be to use a dependency injection framework like Koin which is lightweight and easy to use

I think I've found a solution.
I don't know if its clean, but it works for now.
If you have any advice please let me know!
I refactored my interface like below
Changed my ViewModels to extend from AndroidViewModel instead of ViewModel so I can use the application context
every time I want to call the API I use TTNApiService.create(application).getDeviceValues(deviceId)
interface TTNApiService {
#GET("devices")
suspend fun getDevices(): List<String>
#GET("query/{device-id}")
suspend fun getDeviceValues(#Path("device-id") id: String): List<NetworkValue>
#GET("query")
suspend fun getValues(): List<NetworkValue>
companion object{
private lateinit var BASE_URL : String;
private lateinit var TTN_KEY : String;
fun create(context: Context): TTNApiService{
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
BASE_URL = sharedPreferences.getString("api_url","").toString()
TTN_KEY = sharedPreferences.getString("access_key","").toString()
val loggingInterceptor: HttpLoggingInterceptor =
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
val okHttpClient = OkHttpClient.Builder()
.addInterceptor { chain ->
val originalRequest = chain.request()
val newRequest = originalRequest.newBuilder()
.addHeader("Authorization", "key $TTN_KEY")
.build()
chain.proceed(newRequest)
}
.addInterceptor(loggingInterceptor)
.build()
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.client(okHttpClient)
.build()
.create(TTNApiService::class.java)
}
}
}

Related

baseurl must end in /

I have weather api to parse data for 10 days
All it's good , but I have problem with retrofit now , I have app crashes , my URL(with API) have / in the end.
But still don't working.
Also I have dependency injection for retrofit.
Goal is to get data from api.
Hope , that you will help me to resolve this problem.
package const
const val BASE_URL = "https://api.weatherapi.com/v1/forecast" +
".json?key=a9f9d57b6e064f16b28141346231001&q=London&days=10&aqi=no&alerts=no/" // error here
const val apikey = "a9f9d57b6e064f16b28141346231001"
const val WeatherDays = 10
interface WeatherServiceAPI {
#GET("forecast.json")
suspend fun Weatherday(
#Query("days") days : Int
) : WeatherResponse
#GET("forecast.json")
suspend fun searchcitybycoord(#Query("lat")lat:String) : List<WeatherLocationDTO>
#GET("forecast.json")
suspend fun searchingbyCity(#Query("q") name: String) : List<WeatherLocationDTO>
companion object{
operator fun invoke(
connectivityInterceptor: Interceptor
):WeatherServiceAPI{
val requestInterceptor = Interceptor{
chain -> val url = chain.request()
.url
.newBuilder()
.addQueryParameter("key", apikey)
.build()
val request = chain.request()
.newBuilder()
.url(url)
.build()
return#Interceptor chain.proceed(request)
}
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(requestInterceptor)
.addInterceptor(connectivityInterceptor)
.build()
return Retrofit.Builder()
.client(okHttpClient)
.baseUrl("https://api.weatherapi.com/v1/") // error line
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(WeatherServiceAPI::class.java)
}
}
}
#Provides
#Singleton
fun providerepository(api:WeatherServiceAPI):ForecastRepository{
return ForecastRepositoryImpl(api)
}
#Provides
#Singleton
fun provideWeatherApiService(retrofit: Retrofit) =
retrofit.create(WeatherServiceAPI::class.java)
#Provides
#Singleton
fun provideRetrofit ( okHttpClient: OkHttpClient) = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.client(okHttpClient)
.build()
#Provides
#Singleton
fun provideOkhttpClient(interceptor: Interceptor): OkHttpClient {
val httpBuilder = OkHttpClient.Builder().addInterceptor(interceptor)
return httpBuilder.build()
}
#Provides
#Singleton
fun provideinterceptor():Interceptor{
return Interceptor {
val request =it.request().newBuilder()
val actualRequest = request.build()
it.proceed(actualRequest)
}
}
Given what else you have in the code, your base URL should be https://api.weatherapi.com/v1/.
forecast.json comes from the #GET annotations, and the query parameters will need to come from #Query-annotated parameters to your Retrofit interface functions.

OkHttp client generates diffrent URL for retrofit

I am developing a simple app to fetch now playing movie details from https://www.themoviedb.org/ API.
This is the URL to which I need to perform the API call.
https://api.themoviedb.org/3/movie/now_playing?api_key=<<api_key>>
I am using retrofit to make the API call like this.
#GET("/movie/now_playing")
fun getNowPlayingMovies(): Single<List<MovieData>>
and I am using Base Url and API key as variables in the client class.
private val BASE_URL = "https://api.themoviedb.org/3/"
private val API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
Since the best way to add the api key as a query parameter to the URL is by using a request Interceptor , I created an intercepted to intercept the request and add the api key as a query parameter.
private fun getInterceptor() : Interceptor{
if (requestInterceptor == null){
requestInterceptor = Interceptor{
val url = it.request()
.url
.newBuilder()
.addQueryParameter("api_key" , API_KEY)
.build()
val request = it.request()
.newBuilder()
.url(url)
.build()
return#Interceptor it.proceed(request)
}
}
return requestInterceptor
}
Then added this interceptor along with logging interceptor to the OkHttp Client.
private fun getOkHttpClient() : OkHttpClient{
var httLog : HttpLoggingInterceptor = HttpLoggingInterceptor()
httLog.setLevel(HttpLoggingInterceptor.Level.BODY)
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(getInterceptor()).addInterceptor(httLog)
.connectTimeout(60 , TimeUnit.SECONDS)
.build()
return okHttpClient
}
And then build the retrofit client. In which I add the base URL.
var retrofit : Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(getOkHttpClient())
.addConverterFactory(getGsonConverterFactory())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
The problem is ,
the entire URL made using the BASE_URL and the api as a Query Paramter , Should be like this
https://api.themoviedb.org/3/movie/now_playing?api_key=**********
But in the logging interceptor I am getting this as the url
https://api.themoviedb.org/movie/now_playing?api_key=**********
which has a different BASE_URL than the one I provided in retrofit. It is giving me a 404 error.
I cannot seem to figure out why this is causing.
I logged on different places and I believe the request Interceptor is intercepting a different URL than the BASE_URL.
This is my entire codebase, it would be very helpful if there are any other improvements in the code , I am new to android development and Kotlin. Thank You.
interface MoviesApiServiceRx {
#GET("/movie/now_playing")
fun getNowPlayingMovies(): Single<List<MovieData>>
}
class MoviesRetrofitClient() {
private val BASE_URL = "https://api.themoviedb.org/3/"
private val API_KEY = "9a976526fce8c29aaa35eb4a1e654d3c"
private var moviesApiServiceRx : MoviesApiServiceRx
private var gsonConverterFactory : GsonConverterFactory
private var requestInterceptor : Interceptor
init {
moviesApiServiceRx = getMoviesApiServiceRx()
gsonConverterFactory = getGsonConverterFactory()
requestInterceptor = getInterceptor()
}
private fun getInterceptor() : Interceptor{
if (requestInterceptor == null){
requestInterceptor = Interceptor{
val url = it.request()
.url
.newBuilder()
.addQueryParameter("api_key" , API_KEY)
.build()
val request = it.request()
.newBuilder()
.url(url)
.build()
return#Interceptor it.proceed(request)
}
}
return requestInterceptor
}
private fun getGsonConverterFactory() : GsonConverterFactory{
if (gsonConverterFactory == null){
gsonConverterFactory = GsonConverterFactory.create();
}
return gsonConverterFactory
}
private fun getOkHttpClient() : OkHttpClient{
var httLog : HttpLoggingInterceptor = HttpLoggingInterceptor()
httLog.setLevel(HttpLoggingInterceptor.Level.BODY)
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(getInterceptor()).addInterceptor(httLog)
.connectTimeout(60 , TimeUnit.SECONDS)
.build()
return okHttpClient
}
private fun getMoviesApiServiceRx() : MoviesApiServiceRx{
if (moviesApiServiceRx == null){
var retrofit : Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(getOkHttpClient())
.addConverterFactory(getGsonConverterFactory())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
moviesApiServiceRx = retrofit.create(MoviesApiServiceRx::class.java)
}
return moviesApiServiceRx
}
fun getNowPlayingMovies(): Single<List<MovieData>> {
return getMoviesApiServiceRx().
getNowPlayingMovies()
}
}
Change
#GET("/movie/now_playing")
to
#GET("movie/now_playing")
The / at the beginning refers to the root so it will replace whatever there is after the service name.
two options
interface MoviesApiServiceRx {
#GET("/3/movie/now_playing")
fun getNowPlayingMovies(): Single<List<MovieData>>
}
private val BASE_URL = "https://api.themoviedb.org/"
or
interface MoviesApiServiceRx {
#GET("movie/now_playing")
fun getNowPlayingMovies(): Single<List<MovieData>>
}
private val BASE_URL = "https://api.themoviedb.org/3/"

Android make POST request with retrofit

I'm trying to make my first POST request to make the user login using retrofit library, but it's not working and i don't understand why. If i make a GET request it works, but with POST something gone wrong and i don't understand why. My API run on localhost webserver
My code of the LoginService:
private const val BASE_URL = "http://localhost:10000/api/"
/**
* Build the Moshi object that Retrofit will be using, making sure to add the Kotlin adapter for
* full Kotlin compatibility.
*/
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
/**
* Use the Retrofit builder to build a retrofit object using a Moshi converter with our Moshi
* object.
*/
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
interface LoginApiService {
#Headers("Content-Type: application/json")
#POST("login")
suspend fun makeLogin(#Body usr: User): LoginResponse
}
/**
* A public Api object that exposes the lazy-initialized Retrofit service
*/
object LoginApi {
val retrofitService : LoginApiService by lazy { retrofit.create(LoginApiService::class.java) }
}
code of the LoginResponse class
data class LoginResponse(
val token: String,
val expiration: Date,
val role: Int)
code of the User class:
data class User(
val mail: String,
val pw: String
) : Parcelable
Code of the ViewModel that make the request:
private fun makeLogin(email: String, password: String) {
viewModelScope.launch {
try {
val usr = User(email, password)
val rsp = LoginApi.retrofitService.makeLogin(usr)
_isLogged.value = true
} catch (ex: Exception) {
_status.value = LoginStatus.ERROR
}
}
}
Can someone help me to solve this please? it seems that the request it's not sended.
my retrofit call generate this error in logcat in the try-catch block
java.lang.IllegalArgumentException: Unable to create converter for class com.example.ticketapp.network.LoginResponse
for method LoginApiService.makeLogin
Default Retrofit's timeout is 10sec. You can fix it like this:
val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
val retrofit = Retrofit.Builder()
...
.client(client)
.build()
Here I set it to 30secs, but you can use any number and TimeUnit you want.
UPD:
You can store Retorfit builder in a separate file like this:
interface WebService {
companion object {
fun <T> build(clazz: Class<T>): T {
val client = OkHttpClient.Builder()
...
.build()
val retrofit = Retrofit.Builder()
...
.build()
return retrofit.create(clazz)
}
}
}
Then you can have multiple ApiService interfaces. And use them like this:
val myApiService = WebService.build(MyApiServiceInterface::class.java)
myApiService.myRequestFunction()
Try to add
android:usesCleartextTraffic="true"
Into your application tag in manifest

SharedPreferences instance inside NetworkModule

I am facing with SharedPreferences problem. I would like to know how I can call SharedPreferences inside Retrofit. I mean, I have this following file :
#Module
class NetworkModule {
#Provides
internal fun provideGson(): Gson {
return GsonBuilder().create()
}
#Provides
internal fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder().addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder().addHeader("Accept", "application/json")
val request = requestBuilder.method(original.method(), original.body()).build()
chain.proceed(request)
}.build()
}
#Provides
internal fun provideRetrofit(gson: Gson, okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(BASE_URL)
.client(okHttpClient)
.build()
}
}
And in my Presenter, I have this following code:
override fun getSavedToken() {
mToken = mSharedPreferences.getString(TOKEN, TOKEN_UNAVAILABLE)
}
...
inner class GetAccessTokenSubscriber : ResourceObserver<AccessTokenBean>() {
override fun onNext(#NonNull accessToken: AccessTokenBean) {
mSharedPreferences.edit().putString(TOKEN, accessToken.token).apply()
getInformation()
}
override fun onError(#NonNull e: Throwable) {
mView?.displayError()
}
override fun onComplete() {
// Nothing to do
}
}
Currently, to set the token I put the Bearer $token in my repository / service
// Repository
val newToken = "Bearer $token"
return mService.getInfos(newToken)
// Service
fun getInfos(#Header("Authorization") token: String
I would like to know how I can put the Bearer + token inside my NetworkModule file?
Thank you for your time.
If you want to place the value on the interceptor, just call your SharedPreferences instance on the interceptor provider:
#Provides
internal fun provideOkHttpClient(sharedPrefs: SharedPrefs): OkHttpClient {
return OkHttpClient.Builder().addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder().addHeader("Accept", "application/json")
val request = requestBuilder.method(original.method(), original.body()).build()
chain.proceed(request)
}.build()
}
Now dagger will look for that, but it won't find it, giving you an error. In that case, if you network module is also a singleton too just add a includes = [PreferencesModule::class], if not, you may need to set the current component dependent on the Singleton where you preferences module is located.

Retrofit with kotlin, unable to create #Body

Hi i get the following error:
java.lang.IllegalArgumentException: Unable to create #Body converter for class com.jr.app.models.ExampleData (parameter #1)
Here is my ExampleData.kt
data class ExampleData(val id: String,
val firstName: String,
val secondName: String,
val profilImages: String,
val info: String) {
}
My interface retrofit
interface UsersService {
#GET("/usersProfile")
fun getAllUsers(): Call<List<ExampleData>>
#POST("/usersProfile")
fun addUser(#Body exampleData: ExampleData): Call<ResponseBody>
}
function that addsUser
override fun addUser(user: ExampleData) {
val retrofit = Retrofit.Builder().baseUrl(baseUrl).client(httpAuthClient).build();
val userService = retrofit.create(UsersService::class.java);
userService.addUser(user).enqueue(callbackResponse);
}
private val httpAuthClient: OkHttpClient
get() {
val okHttpClient = OkHttpClient().newBuilder().addInterceptor { chain ->
val originalRequest = chain.request()
val builder = originalRequest.newBuilder().header(authorizeHeader,
Credentials.basic(userName, password))
val newRequest = builder.build()
chain.proceed(newRequest)
}.build()
return okHttpClient
}
Add the gradle dependency to your project:
compile 'com.squareup.retrofit2:converter-gson:VERSION_OF_RETROFIT'
When you build an instance of retrofit add this line:
.addConverterFactory(GsonConverterFactory.create())
In your project building the retrofit object will look like:
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(httpAuthClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
I believe this has nothing to do with Kotlin but your Retrofit configuration and your data class ExampleData.
Retrofit has no idea how to serialize your instance of ExampleData to JSON. You need to add a specific converter factory when creating instance of Retrofit client (see Builder#addConverterFactory method for details).

Categories

Resources