Mehedi Hassan Piash [Sr. Software Engineer]

November 19, 2022

Authenticate api if accessToken expired in retrofit api client in android [Kotlin]

November 19, 2022 Posted by Piash , No comments

 

// retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.9.3"
/**
* Interceptor to add auth token to requests
*/

class AuthInterceptor(context: Context) : Interceptor {
private val sessionManager = SessionManager(context)

override fun intercept(chain: Interceptor.Chain): Response {
val requestBuilder = chain.request().newBuilder()
sessionManager.fetchAuthToken()?.let {
requestBuilder.addHeader(
"Authorization", "Bearer $it"
)
}
return chain.proceed(requestBuilder.build())
}
}
/**
* Authenticator to get the RefreshToken if current token is expired
*/

class AuthenticateApi(context: Context) : Authenticator {
private val sessionManager = SessionManager(context)

override fun authenticate(route: Route?, response: Response): Request? {
val apiClient = Retrofit.Builder().baseUrl(ApiUrl.BASE_URL) // api base url
.addConverterFactory(GsonConverterFactory.create()).build()
.create(ApiService::class.java)
return runBlocking {
// call login api again for getting accessToken in runBlocking so that it will wait until api response come
val apiResponse = apiClient.login(LoginBody("piash599@gmail.com", "p1234", "user"))
if (apiResponse.isSuccess) {
val accessToken = apiResponse.data?.accessToken
accessToken?.let {
sessionManager.saveAuthToken(accessToken)
}
response.request.newBuilder().addHeader(
"Authorization", "Bearer $accessToken"
).build()
} else {
null
}
}
}
}
interface ApiService {
@POST(ApiUrl.USER_LOGIN)
suspend fun login(@Body loginBody: LoginBody): BaseModel<LoginResponse>
}
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
/**
* Provides BaseUrl as string
*/

@Singleton
@Provides
fun provideBaseURL(): String {
return ApiUrl.BASE_URL
}

/**
* Provides LoggingInterceptor for api information
*/

@Singleton
@Provides
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
}

/**
* Provides Auth interceptor for access token
*/

@Singleton
@Provides
fun provideAuthInterceptor(@ApplicationContext context: Context): AuthInterceptor {
return AuthInterceptor(context)
}

/**
* Provides AuthenticateApi to check api is authenticate or not
*/

@Singleton
@Provides
fun provideAuthenticateApi(@ApplicationContext context: Context): AuthenticateApi {
return AuthenticateApi(context)
}

/**
* Provides custom OkkHttp
*/

@Singleton
@Provides
fun provideOkHttpClient(
loggingInterceptor: HttpLoggingInterceptor,
authInterceptor: AuthInterceptor,
authenticateApi: AuthenticateApi
)
: OkHttpClient {
val okHttpClient = OkHttpClient().newBuilder()

okHttpClient.callTimeout(40, TimeUnit.SECONDS)
okHttpClient.connectTimeout(40, TimeUnit.SECONDS)
okHttpClient.readTimeout(40, TimeUnit.SECONDS)
okHttpClient.writeTimeout(40, TimeUnit.SECONDS)
okHttpClient.addInterceptor(loggingInterceptor)
okHttpClient.addInterceptor(authInterceptor)
okHttpClient.authenticator(authenticateApi)
okHttpClient.build()
return okHttpClient.build()
}
/**
* Provides converter factory for retrofit
*/

@Singleton
@Provides
fun provideConverterFactory(): Converter.Factory {
return GsonConverterFactory.create()
}

/**
* Provides ApiServices client for Retrofit
*/

@Singleton
@Provides
fun provideRetrofitClient(
baseUrl: String, okHttpClient: OkHttpClient, converterFactory: Converter.Factory
)
: Retrofit {
return Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(converterFactory)
.build()
}
/**
* Provides Api Service using retrofit
*/

@Singleton
@Provides
fun provideRestApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}

}
/**
* Session manager to save and fetch data from SharedPreferences
*/

class SessionManager (context: Context) {
private var prefs: SharedPreferences = context.getSharedPreferences(context.getString(R.string.app_name), Context.MODE_PRIVATE)

companion object {
const val USER_TOKEN = "user_token"
}

/**
* Function to save auth token
*/

fun saveAuthToken(token: String) {
val editor = prefs.edit()
editor.putString(USER_TOKEN, token)
editor.apply()
}

/**
* Function to fetch auth token
*/

fun fetchAuthToken(): String? {
return prefs.getString(USER_TOKEN, null)
}

/**
* Function to fetch auth token
*/

fun clearAuthToken() {
prefs.edit().clear().commit()
}
}

September 03, 2022

Exposed BaseTable for generating createdAt and updatedAt in Ktor part-4

September 03, 2022 Posted by Piash , No comments

 ktor BaseTable in terms of IntIdTable

import org.jetbrains.exposed.dao.*
import org.jetbrains.exposed.sql.ReferenceOption
import org.joda.time.DateTime
import org.joda.time.DateTimeZone

fun currentUtc(): DateTime = DateTime.now(DateTimeZone.UTC)

abstract class BaseIntIdTable(name: String) : IntIdTable(name) {
val createdAt = datetime("createdAt").clientDefault { currentUtc() }
val updatedAt = datetime("updatedAt").nullable()
}

abstract class BaseIntEntity(id: EntityID<Int>, table: BaseIntIdTable) : IntEntity(id) {
val createdAt by table.createdAt
var updatedAt by table.updatedAt
}

abstract class BaseIntEntityClass<E : BaseIntEntity>(table: BaseIntIdTable) : IntEntityClass<E>(table) {

init {
EntityHook.subscribe { action ->
if (action.changeType == EntityChangeType.Updated) {
try {
action.toEntity(this)?.updatedAt = currentUtc()
} catch (e: Exception) {
//nothing much to do here
}
}
}
}
}

val BaseIntEntity.idValue: Int
get() = this.id.value

object Users : BaseIntIdTable("users") {
val name = varchar("name", length = 60)
val role = reference("roleId", Roles, onDelete = ReferenceOption.NO_ACTION).nullable()
}

class User(id: EntityID<Int>) : BaseIntEntity(id, Users) {
companion object : BaseIntEntityClass<User>(Users)

var name by Users.name
var role by Role optionalReferencedOn Users.role
}
import org.jetbrains.exposed.dao.*
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.javatime.datetime
import java.time.LocalDateTime
import java.time.ZoneOffset
import java.util.UUID
// generating utc time
fun currentUtc(): LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
abstract class BaseIntIdTable(name: String) : IdTable<String>(name) {
override val id: Column<EntityID<String>> =varchar("id", 50).clientDefault { UUID.randomUUID().toString() }.entityId()
val createdAt = datetime("created_at").clientDefault { currentUtc() }
val updatedAt = datetime("updated_at").nullable()
}

abstract class BaseIntEntity(id: EntityID<String>, table: BaseIntIdTable) : Entity<String>(id) {
val createdAt by table.createdAt
var updatedAt by table.updatedAt
}
abstract class BaseIntEntityClass<E : BaseIntEntity>(table: BaseIntIdTable) : EntityClass<String, E>(table){
init {
EntityHook.subscribe { action ->
if (action.changeType == EntityChangeType.Updated) {
try {
action.toEntity(this)?.updatedAt = currentUtc()
} catch (e: Exception) {
//nothing much to do here
}
}
}
}
}

object Users : BaseIntIdTable("users") {
val name = varchar("name", length = 60)
val role = reference("roleId", Roles, onDelete = ReferenceOption.NO_ACTION).nullable()
}

class User(id: EntityID<Int>) : BaseIntEntity(id, Users) {
companion object : BaseIntEntityClass<User>(Users)

var name by Users.name
var role by Role optionalReferencedOn Users.role
}

Ref:
https://piashcse.medium.com/exposed-basetable-for-generating-createdat-and-updatedat-in-ktor-part-3-581511fdfa38

May 27, 2022

Modern redux architecture with redux-saga and redux-toolkit

May 27, 2022 Posted by Piash , No comments

 Redux is the most popular library for state management in react and react-native app development. On the other redux-saga is a middleware library used to allow a Redux store to interact with resources outside of itself asynchronously. So it's almost common to use redux-saga with redux.

import {createSlice} from '@reduxjs/toolkit'

const movieDetailState = createSlice({
name: 'movieDetail', initialState: {
movieDetail: {}, isLoading: false,
}, reducers: {
getMovieDetail: (state, action) => {
state.isLoading = true
}, movieDetailSuccess: (state, action) => {
state.movieDetail = action.payload;
state.isLoading = false
}, movieDetailFailure: (state) => {
state.isLoading = false
}
}
});
export const {getMovieDetail, movieDetailSuccess, movieDetailFailure} = movieDetailState.actions;
export default movieDetailState.reducer
import {configureStore} from '@reduxjs/toolkit'
import combineReducers from './reducer';
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas';
import logger from 'redux-logger';

const sagaMiddleware = createSagaMiddleware();
const middleware = [sagaMiddleware];

const configurationAppStore = () => {
const store = configureStore({
reducer: combineReducers, middleware: [...middleware, logger], devTools: process.env.NODE_ENV === 'development'
})
sagaMiddleware.run(rootSaga);
return store
}
export default configurationAppStore
import {takeEvery, call, put} from 'redux-saga/effects';
import AxiosService from '../../../networks/AxiosService';
import {ApiUrls} from '../../../networks/ApiUrls';
import {movieDetailSuccess, movieDetailFailure, getMovieDetail} from './../../reducer/moviedetail'
function* movieDetailApi(action) {
try {
const response = yield call(AxiosService.getServiceData, ApiUrls.MOVIE_DETAIL(action.payload.movieId), {});
const result = response.data;
yield put(movieDetailSuccess(result));
} catch (error) {
yield put(movieDetailFailure());
}
}
const combineSagas = [takeEvery(takeEvery(getMovieDetail.type, movieDetailApi)];
export default combineSagas
import {all} from 'redux-saga/effects';
import combineSagas from "./movielist";

export default function* rootSaga() {
yield all([...combineSagas]);
}
import React from 'react';
import configureStore from './src/redux';
import {Provider} from 'react-redux';
import Navigation from './src/navigation/AppNavigation';

const store = configureStore();
const App = () => {
return (
<Provider store={store}>
<Navigation />
</Provider>
);
};

export default App;
import React, {useEffect} from 'react';
import {useSelector, useDispatch} from 'react-redux';
import Loading from '../../components/loading/Loading';
import styles from './MovieDetailStyle'
import {FlatList, Image, Text, TouchableOpacity, View, ScrollView} from "react-native";
import {Constants} from "../../appconstants/AppConstants";
import {getMovieDetail} from './../../redux/reducer/moviedetail';

const MovieDetail = ({navigation, route}) => {
const {movieId} = route.params
//communicate with redux
const {isLoading, movieDetail} = useSelector(state => state.movieDetailReducer);

// Api call
useEffect(() => {
dispatch(getMovieDetail({movieId}))
}, [])

// main view with loading while api call is going on
return isLoading ? <Loading/> : (<ScrollView style={styles.mainView}>
<Image
style={styles.imageView}
source={{
uri: `${Constants.IMAGE_URL}${movieDetail?.poster_path}`,
}}/>
<View style={styles.secondContainer}>
<Text style={styles.title}>{movieDetail.title}</Text>
<View style={styles.thirdContainer}>
<View style={styles.fourthContainer}>
<Text style={styles.infoTitleData}>{movieDetail.original_language}</Text>
<Text style={styles.infoTitle}>Language</Text>
</View>
<View style={styles.fourthContainer}>
<Text style={styles.infoTitleData}>{movieDetail.vote_average}</Text>
<Text style={styles.infoTitle}>Rating</Text>
</View>
<View style={styles.fourthContainer}>
<Text style={styles.infoTitleData}>{movieDetail.runtime} min</Text>
<Text style={styles.infoTitle}>Duration</Text>
</View>
<View style={styles.fourthContainer}>
<Text style={styles.infoTitleData}>{movieDetail.release_date}</Text>
<Text style={styles.infoTitle}>Release Date</Text>
</View>
</View>
<Text style={styles.description}>Description</Text>
<Text>{movieDetail.overview}</Text>
<Text style={styles.description}>Similar</Text>
</View>
</ScrollView>)
}
export default MovieDetail

March 31, 2022

ExoPlayer in Android Part-1 [kotlin]

March 31, 2022 Posted by Piash , No comments

 Sometimes we need the player to play our media either video or audio. Exoplayer is the best choice to play our video and audio.

implementation 'com.google.android.exoplayer:exoplayer:2.17.1'
<string name="media_url_mp3">https://storage.googleapis.com/exoplayer-test-media-0/Jazz_In_Paris.mp3</string>
<!-- Big Buck Bunny video provided by the Blender Foundation.
(c) copyright 2008, Blender Foundation / www.bigbuckbunny.org -->
<string name="media_url_mp4">https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.mp4</string>
<string name="media_url_dash"><![CDATA[https://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0]]></string>
<string name="logo">Google logo</string>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black">

<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:show_buffering="when_playing" />

</FrameLayout>
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.util.Util
import com.piashcse.experiment.mvvm_hilt.R
import com.piashcse.experiment.mvvm_hilt.databinding.FragmentExoPlayerBinding


class ExoPlayerFragment : Fragment() {
private var _binding: FragmentExoPlayerBinding? = null
private val binding get() = requireNotNull(_binding)

private var player: ExoPlayer? = null

private var playWhenReady = true
private var currentWindow = 0
private var playbackPosition = 0L

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
_binding = FragmentExoPlayerBinding.inflate(layoutInflater, container, false)
return binding.root
}

override fun onStart() {
super.onStart()
if (Util.SDK_INT > 23) {
initializePlayer()
}
}

override fun onResume() {
super.onResume()
hideSystemUi()
if (Util.SDK_INT <= 23 || player == null) {
initializePlayer()
}
}

override fun onPause() {
super.onPause()
if (Util.SDK_INT <= 23) {
releasePlayer()
}
}

override fun onStop() {
super.onStop()
if (Util.SDK_INT > 23) {
releasePlayer()
}
}

private fun initializePlayer() {
player = ExoPlayer.Builder(requireContext())
.build()
.also { exoPlayer ->
binding.videoView.player = exoPlayer

val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp4))
exoPlayer.setMediaItem(mediaItem)
// val secondMediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3))
// exoPlayer.addMediaItem(secondMediaItem)
exoPlayer.playWhenReady = playWhenReady
exoPlayer.seekTo(currentWindow, playbackPosition)
exoPlayer.prepare()
}
}

private fun releasePlayer() {
player?.run {
playbackPosition = this.currentPosition
currentWindow = this.currentMediaItemIndex
playWhenReady
= this.playWhenReady
release()
}
player = null
}

@SuppressLint("InlinedApi")
private fun hideSystemUi() {
binding.videoView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE
or View.SYSTEM_UI_FLAG_FULLSCREEN
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
}
}