I use OkHttp3 in Kotlin as a HTTP client and send Authorization Token Bearer header in request and return the result.
this is my code, but when run it the app closed :
RetrofitInstance.kt
class OAuthInterceptor(
private val tokenType: String,
private val acceessToken: String,
private val branchid : Int,
private val currency : Int,):Interceptor {
override fun intercept(chain: Interceptor.Chain): okhttp3.Response
{
var request = chain.request()
request = request.newBuilder()
.addHeader("Authorization", "$tokenType $acceessToken")
.addHeader("branchId", "$branchid")
.addHeader("currencyId", "$currency")
.build()
return chain.proceed(request)
}
}
class RetrofitInstance {
companion object {
private val client = OkHttpClient.Builder()
.addInterceptor(OAuthInterceptor(
"Bearer",
"YourToken",
123,
1)
)
.build()
val BASE_URL = "https://url"
fun getRetrofitInstance(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client).addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
.build()
}
}
}
AlbumService.Kt
interface AlbumService {
#GET("users")
suspend fun getAlbums() : Response<Albums>
}
Album.kt
class Albums : ArrayList<dataapi>()
AlbumItem.kt
data class dataapi(
#SerializedName("address")
val address: String,
)
this is a self answered question about adding access token to request header and refresh the token with refresh token, I was struggling with this topic for a long time, and now I'm writing in post hopefully it could help anyone else in same situations
maybe there would be any other better solutions, but it worked for me in the easiest way
in remote module I'm following this method with the help of Hilt:
#Module
#InstallIn(SingletonComponent::class)
object NetworkModule {
#Provides
#Singleton
fun providesRetrofit (okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
#Provides
#Singleton
fun providesOkHttpClient(interceptor: AuthInterceptor, authAuthenticator: AuthAuthenticator): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(interceptor)
.authenticator(authAuthenticator)
.build()
}
I send a request to server and receive the access token and refresh token, then I saved them with the power of shared preferences like so:
class TokenManager #Inject constructor(#ApplicationContext context: Context) {
private var prefs: SharedPreferences =
context.getSharedPreferences(PREFS_TOKEN_FILE, Context.MODE_PRIVATE)
fun saveToken(token: UserAuthModel?) {
val editor = prefs.edit()
token?.let {
editor.putString(USER_TOKEN, token.access_token).apply()
editor.putString(USER_REFRESH_TOKEN,token.refresh_token).apply()
editor.putBoolean(IS_LOGGED_IN,true).apply ()
}
}
fun getToken(): String? {
return prefs.getString(USER_TOKEN, null)
}
fun getRefreshToken(): String? {
return prefs.getString(USER_REFRESH_TOKEN, null)
}}
then I use .addInterceptor(interceptor) in order to add header to all requests like this:
class AuthInterceptor #Inject constructor():Interceptor{
#Inject
lateinit var tokenManager: TokenManager
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
val token = tokenManager.getToken()
request.addHeader("Authorization", "Bearer $token")
request.addHeader("Accept","application/json")
return chain.proceed(request.build())
}}
after that you will have access to every method which requires access token as authentication modo, depending on your API instruction your access token will be expired in a specific time ( maybe 24 hours) and you need a new access token which is accessible with help of refresh token that you already have it, and then I add this line to okHttp .authenticator(authAuthenticator)
when your access token expires, the API will send you back a 401 or 403 error code (it will happen in interceptor section), and in that time Authenticator came into play, luckily it's smart enough to recognize this and do the task,
I take care of Authenticator like this:
class AuthAuthenticator #Inject constructor() : Authenticator {
#Inject lateinit var tokenManager: TokenManager
override fun authenticate(route: Route?, response: Response): Request? {
return runBlocking {
val refreshToken=tokenManager.getRefreshToken()
val refreshTokenR:RequestBody= refreshToken?.toRequestBody() ?: "".toRequestBody()
val grantTypeR:RequestBody= "refresh_token".toRequestBody()
//val newAccessToken = authService.safeRefreshTokenFromApi(refreshToken,grantType)
val newAccessToken = getUpdatedToken(refreshTokenR,grantTypeR)
if (!newAccessToken.isSuccessful){
val intent=Intent(context,MainActivity::class.java)
context.startActivity(intent)
}
tokenManager.saveToken(newAccessToken.body()) // save new access_token for next called
newAccessToken.body()?.let {
response.request.newBuilder()
.header("Authorization", "Bearer ${it.access_token}") // just only need to
override "Authorization" header, don't need to override all header since this new request
is create base on old request
.build()
}
} }
private suspend fun getUpdatedToken( refreshToken:RequestBody,grantType:RequestBody):
retrofit2.Response<UserAuthModel> {
val okHttpClient = OkHttpClient.Builder()
.build()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
val service=retrofit.create(AuthService::class.java)
return service.refreshTokenFromApi(refreshToken,grantType)
}}
Authenticator need to make a request, so it needs a retrofit and OkHttp instance( which will run this very Authenticator),in order to break this cycle I created another instance .
two things I have to mention is :
I guess it's ok to use runBlocking because Authenticator itself is running on another thread
and remember in case of kotlin you have to use suspend function in API service to take care of Unable to create call adapter for retrofit2.Response error
at the end, I have to mention that I'm using two different API service like this:
general service:
interface MovieService {
#GET("api/v1/movies/{movie-id}")
suspend fun getSingleMovie(#Path("movie-id") movieId:Int):Response<NetworkMovieModel>}
authentication service:
interface AuthService:MovieService {
#Multipart
#POST("oauth/token")
fun refreshTokenFromApi (#Part("refresh_token") username: RequestBody,
#Part("grant_type") grantType: RequestBody
): Response<UserAuthModel>}
I'm new to Kotlin and having trouble to change the existing POST request parameters to body instead. I looked at other answers but none of them have the similar code as mine for the request part. I don't know how to change it just getting a lot of syntax errors. Thanks!
import retrofit2.Call
import retrofit2.http.*
interface PostInterface {
#POST("signin")
fun signIn(#Query("email") email: String, #Query("password") password: String): Call<String>
}
class BasicRepo #Inject constructor(val postInterface: PostInterface) {
fun signIn(email: String, password: String): MutableLiveData<Resource> {
val status: MutableLiveData<Resource> = MutableLiveData()
status.value = Resource.loading(null)
postInterface.signIn(email, password).enqueue(object : Callback<String> {
override fun onResponse(call: Call<String>, response: Response<String>) {
if (response.code() == 200 || response.code() == 201) {
// do something
} else {
// do something
}
}
}
}
}
class User constructor(
email: String,
password: String
)
#POST("signin")
suspend fun signIn(
#Body body: User,
): ResponseBody
Btw, You can use body instead of query params only if your API supports it.
Also, I recommend using a ResultWrapper. Handling errors with Retrofit and Coroutines in a single place
I am new to dagger 2 and kotlin both. Getting lateinit property not initialized.
I have a module which have few #Provides methods but one of the class not able to create object which used #Inject and lateinit.
Login service takes "LoginAPI" as parameter and works fine but as i want all my login related API's to use the same service. There is one more API related "LoginWithOrgAPI".
Now my need is to get any API object when needed in the LoginService class. So i tries using lateinit with #Inject as show in LoginService class but its not working.
#Module(includes = [(NetworkingModule::class)])
class LoginModule {
#Provides
fun provideLoginApi(retrofit: Retrofit): LoginApi =
retrofit.create(LoginApi::class.java)
#Provides
fun provideLoginWithOrgApi(retrofit: Retrofit): LoginWithOrgApi =
retrofit.create(LoginWithOrgApi::class.java)
#Provides
fun provideLoginService(api: LoginApi): LoginService =
LoginService(api)
#Provides
fun provideLoginInteractor(apiService: LoginService): LoginInteractor =
LoginInteractor(apiService)
}
// adding LoginService class
class LoginService(val loginAPI: LoginApi) {
#Inject
lateinit var loginWithOrgApi: LoginWithOrgApi
fun loginAPIService(user: String, password: String?, extension: String, otp: String?,
hardwareId: String): Single<LoginAPIResponseData> {
password?.let {
return loginAPI.getLogin(user, it, extension, null, hardwareId)
}?: run {
return loginAPI.getLogin(user, null, extension, otp, hardwareId)
}
}
fun loginWithOrg(user: String, password: String?, extension: String, otp: String?,
userId: String, hardwareId: String): Single<LoginAPIResponseData>{
password?.let {
return loginWithOrgApi.getLogin(user, it, extension, null, userId, hardwareId)
}?: run {
return loginWithOrgApi.getLogin(user, null, extension, otp, userId, hardwareId)
}
}
}
// component
#Component(modules = [(LoginModule::class)])
interface LoginComponent {
fun loginInteractor(): LoginInteractor
}
// api interface
interface LoginWithOrgApi {
#POST("access/v1/login/")
#FormUrlEncoded
fun getLogin(#Field("user") user: String,
#Field("password") password: String?,
#Field("mobile_extension") extension: String,
#Field("otp") otp: String?,
#Field("user_id") userId: String,
#Field("hardware_id") hardwareId: String): Single<LoginAPIResponseData>
}
Getting the crash saying "lateinit" property not initialized when trying to call method "loginWithOrg"
My understanding is that once define and provided through module, i can get the object through #Inject in the dependency graph but something is missing here.
// my objective for LoginService class
class LoginService() {
#Inject
var loginWithOrgApi: LoginWithOrgApi
#Inject
var loginApi: LoginApi
fun loginAPIService(user: String, password: String?, extension: String, otp: String?,
hardwareId: String): Single<LoginAPIResponseData> {
password?.let {
return loginAPI.getLogin(user, it, extension, null, hardwareId)
}?: run {
return loginAPI.getLogin(user, null, extension, otp, hardwareId)
}
}
fun loginWithOrg(user: String, password: String?, extension: String, otp: String?,
userId: String, hardwareId: String): Single<LoginAPIResponseData>{
password?.let {
return loginWithOrgApi.getLogin(user, it, extension, null, userId, hardwareId)
}?: run {
return loginWithOrgApi.getLogin(user, null, extension, otp, userId, hardwareId)
}
}
}
Because you are mixing two independent things: member injection via void inject(MyClass myClass); and constructor injection.
In fact, you even have a separate field that "ought to be member-injected", even though you could technically receive that as a constructor param?
class LoginService(val loginAPI: LoginApi) { // <-- constructor param + field
#Inject
lateinit var loginWithOrgApi: LoginWithOrgApi // <-- why is this a lateinit member injected field?
// this could also be constructor param
So it should be as follows.
#Module(includes = [(NetworkingModule::class)])
class LoginModule {
#Provides
fun loginApi(retrofit: Retrofit): LoginApi =
retrofit.create(LoginApi::class.java)
#Provides
fun loginWithOrgApi(retrofit: Retrofit): LoginWithOrgApi =
retrofit.create(LoginWithOrgApi::class.java)
//#Provides
//fun provideLoginService(api: LoginApi): LoginService =
// LoginService(api)
//#Provides
//fun provideLoginInteractor(apiService: LoginService): LoginInteractor =
// LoginInteractor(apiService)
}
and
class LoginService #Inject constructor(
val loginAPI: LoginApi,
val loginWithOrgApi: LoginWithOrgApi
) {
fun loginAPIService(user: String, password: String?, extension: String, otp: String?,
hardwareId: String): Single<LoginAPIResponseData> {
password?.let { password ->
return loginAPI.getLogin(user, password, extension, null, hardwareId)
}?: run {
return loginAPI.getLogin(user, null, extension, otp, hardwareId)
}
}
fun loginWithOrg(user: String, password: String?, extension: String, otp: String?,
userId: String, hardwareId: String): Single<LoginAPIResponseData>{
password?.let { password ->
return loginWithOrgApi.getLogin(user, password, extension, null, userId, hardwareId)
}?: run {
return loginWithOrgApi.getLogin(user, null, extension, otp, userId, hardwareId)
}
}
}
and
class LoginInteractor #Inject constructor(
val apiService: LoginService
) {
...
}
You own those classes, so there is no reason to use #field:Inject lateinit var + void inject(MyClass myClass); here.
I think somethings wrong about my code in TeamImplsTest, and i need advice :D
This is my code
API interface
interface API {
#GET("lookupteam.php")
fun getTeam(#Query("id") id: String): Call<TeamModel>
}
TeamPresenter
interface MatchPresenter {
fun loadTeamDetail(team_id: String)
}
TeamImpls
class TeamImpls(val teamView: TeamView) : TeamPresenter {
override fun loadTeamDetail(team_id: String) {
val call = RetrofitConfig().getApi().getTeam(team_id)
call.enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful()) {
val res = response.body()
res?.let { teamView.onSuccess(it) }
}
}
override fun onFailure(call: Call, t: Throwable) {
Log.e("PrevMatchFragment", t.toString())
}
})
}
}
TeamModel
data class TeamModel(
val teams: ArrayList
)
data class TeamModeLResult(
val idTeam: String,
val strTeam: String,
val strAlternate: String,
val strSport: String,
val strStadium: String,
val strTeamBadge: String
)
And
This my TeamImplsTest
class TeamImplsTest {
#Mock
private lateinit var teamView: TeamView
#Mock
private lateinit var teamPresenter: TeamPresenter
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
teamPresenter = TeamImpls(teamView)
}
#Test
fun loadTeamDetail() {
val teams = TeamModel(arrayListOf())
val teamId = "133613"
teamPresenter.loadTeamDetail(teamId)
Mockito.verify(teamView).onSuccess(teams)
}
}
i got error
Wanted but not invoked:
teamView.onSuccess(TeamModel(teams=[]));
-> at com.fathurradhy.matchschedule.domain.presenter.TeamImplsTest.loadTeamDetail(TeamImplsTest.kt:34)
Actually, there were zero interactions with this mock.
Wanted but not invoked:
teamView.onSuccess(TeamModel(teams=[]));
-> at com.fathurradhy.matchschedule.domain.presenter.TeamImplsTest.loadTeamDetail(TeamImplsTest.kt:34)
Actually, there were zero interactions with this mock.
You're not mocking the API call as loadTeamDetail creates its own API instance.
To enable you to test the API call behaviour you could provide the API instance through your constructor, e.g.
class TeamImpls(private val api: API, private val teamView: TeamView) : TeamPresenter {
override fun loadTeamDetail(team_id: String) {
val call = api.getTeam(team_id)
This would then allow you to mock the api behaviour and verify the presenter calls the correct method when the call fails/succeeds, e.g.
class TeamImplsTest {
#Mock
private lateinit var teamView: TeamView
#Mock
private lateinit var api: API
#Mock
private lateinit var teamPresenter: TeamPresenter
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
teamPresenter = TeamImpls(api, teamView)
}
#Test
fun loadTeamDetail() {
val teams = TeamModel(arrayListOf())
val teamId = "133613"
// Use retrofit-mock to create your mockResponse.
// See: https://github.com/square/retrofit/tree/master/retrofit-mock
Mockito.`when`(api.getTeam(teamId)).thenReturn(Calls.response(teams)
teamPresenter.loadTeamDetail(teamId)
Mockito.verify(teamView).onSuccess(teams)
}
}