Mehedi Hassan Piash | Senior Software Engineer | Android | iOS | KMP | Ktor | Jetpack Compose | React-Native.

May 27, 2022

Modern redux architecture with redux-saga and redux-toolkit

May 27, 2022 Posted by Mehedi Hassan 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 Mehedi Hassan 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)
}
}

February 11, 2022

Pagination with Paging3 in android

February 11, 2022 Posted by Mehedi Hassan Piash , No comments

Pagination or Endless scrolling is one of the common pain points in recyclerView in android. But Paging3 library just solved it very swiftly. Let’s start with how to implement the paging3 library in real-life projects with API calls.

paging3 in movie app

Step1. build.gradle

dependencies {
implementation "androidx.paging:paging-runtime-ktx:3.1.0"
}

Step-2. ApiService.kt

interface ApiService {
@GET(ApiUrls.POPULAR_MOVIE_LIST) // your desired api end points
suspend fun popularMovieList(@Query("page") page: Int): BaseModel
}

Step-3. BaseModel.kt

data class BaseModel(
@SerializedName("page")
val page: Int,
@SerializedName("results")
val results: List<MovieItem>,
@SerializedName("total_pages")
val totalPages: Int,
@SerializedName("total_results")
val totalResults: Int
)

Step-4. MovieItem.kt

data class MovieItem(
@SerializedName("adult")
val adult: Boolean,
@SerializedName("backdrop_path")
val backdropPath: String,
@SerializedName("genre_ids")
val genreIds: List<Int>,
@SerializedName("id")
val id: Int,
@SerializedName("original_language")
val originalLanguage: String,
@SerializedName("original_title")
val originalTitle: String,
@SerializedName("overview")
val overview: String,
@SerializedName("popularity")
val popularity: Double,
@SerializedName("poster_path")
val posterPath: String,
@SerializedName("release_date")
val releaseDate: String,
@SerializedName("title")
val title: String,
@SerializedName("video")
val video: Boolean,
@SerializedName("vote_average")
val voteAverage: Double,
@SerializedName("vote_count")
val voteCount: Int
)

Step-5. PopularPagingDataSource.kt

class PopularPagingDataSource @Inject constructor(private val apiService: ApiService) :
PagingSource<Int, MovieItem>() {

override fun getRefreshKey(state: PagingState<Int, MovieItem>): Int? {
return state.anchorPosition
}

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MovieItem> {
return try {
val nextPage = params.key ?: 1
val movieList = apiService.popularMovieList(nextPage)
LoadResult.Page(
data = movieList.results,
prevKey = if (nextPage == 1) null else nextPage - 1,
nextKey = movieList.page + 1
)
} catch (exception: IOException) {
Timber.e("exception ${exception.message}")
return LoadResult.Error(exception)
} catch (httpException: HttpException) {
Timber.e("httpException ${httpException.message}")
return LoadResult.Error(httpException)
}
}
}

Step-6. Paging3ViewModel.kt

@HiltViewModel
class Paging3ViewModel @Inject constructor(private val repoPaging: PopularPagingDataSource) :
ViewModel() {
val flow = Pager(
// Configure how data is loaded by passing additional properties to
// PagingConfig, such as prefetchDistance.
PagingConfig(pageSize = 2)
) {
repoPaging
}.flow.cachedIn(viewModelScope)
}

Step-7. adapter_movie_item_paging_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:elevation="2dp"
android:orientation="vertical"
app:cardCornerRadius="16dp">

Step-8. MoviePagingAdapter.kt

class MoviePagingAdapter :
PagingDataAdapter<MovieItem, MoviePagingAdapter.MovieViewHolder>(DataDifferentiator) {
var onItemClick: ((MovieItem) -> Unit)? = null

inner class MovieViewHolder(val bind: AdapterMovieItemPagingLayoutBinding) :
RecyclerView.ViewHolder(bind.root) {
fun bind(item: MovieItem) {
itemView.setOnClickListener {
onItemClick?.invoke(item)
}
}

}

override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
holder.bind.image.loadImage(ApiUrls.IMAGE_URL.plus(getItem(position)?.posterPath))
getItem(position)?.let { holder.bind(it) }
}


override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
val bind = AdapterMovieItemPagingLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return MovieViewHolder(bind)
}

object DataDifferentiator : DiffUtil.ItemCallback<MovieItem>() {

override fun areItemsTheSame(oldItem: MovieItem, newItem: MovieItem): Boolean {
return oldItem.id == newItem.id
}

override fun areContentsTheSame(oldItem: MovieItem, newItem: MovieItem): Boolean {
return oldItem == newItem
}
}

}

Step-9. fragment_paging3.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.paging3.Paging3Fragment">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/paging_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="6dp"/>

<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />

</RelativeLayout>

Step-10. Paging3Fragment.kt

@AndroidEntryPoint
class Paging3Fragment : Fragment() {
private var _binding: FragmentPaging3Binding? = null
private val binding get() = requireNotNull(_binding)
private val viewModel: Paging3ViewModel by viewModels()
private val moviePagingAdapter: MoviePagingAdapter by lazy {
MoviePagingAdapter()
}

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

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
}

private fun initView() = with(binding) {
viewLifecycleOwner.lifecycleScope.launch {
viewModel.flow.catch {
progressBar.hide()
}.collectLatest {
moviePagingAdapter.submitData(it)
}
}
pagingRecycler.apply {
layoutManager = GridLayoutManager(requireContext(), 2)
adapter = moviePagingAdapter
}
moviePagingAdapter.addLoadStateListener {
when {
it.refresh == LoadState.Loading -> {
progressBar.show()
}
it.append == LoadState.Loading -> {
progressBar.show()
}
else -> {
progressBar.hide()
}
}
}
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}


Ref: https://piashcse.medium.com/pagination-with-paging3-in-android-ccaf7030fcc

Github: https://github.com/piashcse/blog_piashcse_code/tree/master/MVVM_Hilt/app/src/main/java/com/piashcse/experiment/mvvm_hilt/ui/paging3  

December 21, 2021

Collapsible toolbar programatically in android

December 21, 2021 Posted by Mehedi Hassan Piash , No comments

Sometimes may need the toolbar collapsible in the initial stage or in a certain condition. In that time we implement the functionality programmatically. Here is the following code or function we can call in a particular condition.

fun setCollapsibleToolbar() {
val params = appBar.layoutParams as CoordinatorLayout.LayoutParams
val behavior = params.behavior as AppBarLayout.Behavior?
if (behavior != null) {
val valueAnimator: ValueAnimator = ValueAnimator.ofInt()
valueAnimator.interpolator = DecelerateInterpolator()
valueAnimator.addUpdateListener { animation ->
behavior.topAndBottomOffset = (animation.animatedValue as Int)!!
appBar.requestLayout()
}
valueAnimator.setIntValues(0, -900)
valueAnimator.duration = 400
valueAnimator.start()
}
}

Ref: https://piashcse.medium.com/collapsible-toolbar-programatically-in-android-988a94e6795e

 

December 18, 2021

Get child data in parent table by backReferencedOn in kotlin Exposed Ktor part-3

December 18, 2021 Posted by Mehedi Hassan Piash , No comments

UserId is a foreign key in UserHasType table . Now if we want to get UserHasTypeTable data as child data in UsersTable we need to point it as val userType by UserHasTypeEntity backReferencedOn UserHasTypeTable.user_id

object UsersTable : IdTable<String>("users") {
override val id: Column<EntityID<String>> = text("id").uniqueIndex().entityId()
val user_name = text("user_name")
val email = text("email")
val password = text("password")
val mobile_number = text("mobile_number").nullable()
val email_verified_at = text("email_verified_at").nullable() // so far unkmown
val remember_token = text("remember_token").nullable()
val verification_code = text("verification_code").nullable() // verification_code
val created_at = datetime("created_at").defaultExpression(CurrentDateTime()) // UTC time
val updated_at = datetime("updated_at").nullable()
val is_verified = text("is_verified").nullable() // email verified by validation code
override val primaryKey = PrimaryKey(id)
}

class UsersEntity(id: EntityID<String>) : Entity<String>(id) {
companion object : EntityClass<String, UsersEntity>(UsersTable)
var user_name by UsersTable.user_name
var email by UsersTable.email
var password by UsersTable.password
var mobile_number by UsersTable.mobile_number
var email_verified_at by UsersTable.email_verified_at
var remember_token by UsersTable.remember_token
var verification_code by UsersTable.verification_code
var created_at by UsersTable.created_at
var updated_at by UsersTable.updated_at
var is_verified by UsersTable.is_verified
val userType by UserHasTypeEntity backReferencedOn UserHasTypeTable.user_id
fun userResponse() = UsersResponse(
id.value,
user_name,
email,
mobile_number,
email_verified_at,
remember_token,
is_verified,
userType.userHasTypeResponse()
)
}

data class UsersResponse(
val id: String,
val userName: String,
val email: String,
val mobileNumber: String?,
val emailVerifiedAt: String?,
val rememberToken: String?,
val isVerified: String?,
var userType: UserHasType
)

UserHasTypeTable and UserHasTypeEntity

object UserHasTypeTable : IdTable<String>("user_has_type") {
override val id: Column<EntityID<String>> = text("id").uniqueIndex().entityId()
val user_id = reference("user_id", UsersTable.id)
val user_type_id = text("user_type_id")
val created_at = text("created_at")
val updated_at = text("updated_at")
override val primaryKey = PrimaryKey(id)
}

class UserHasTypeEntity(id: EntityID<String>) : Entity<String>(id) {
companion object : EntityClass<String, UserHasTypeEntity>(UserHasTypeTable)
var user_id by UserHasTypeTable.user_id
var user_type_id by UserHasTypeTable.user_type_id
var created_at by UserHasTypeTable.created_at
var updated_at by UserHasTypeTable.updated_at
//var users by UsersEntity referencedOn UserHasTypeTable.user_id
fun userHasTypeResponse() = UserHasType(id.toString(), user_type_id)
}

data class UserHasType(
val id: String, val user_type_id: String
)

User controller

class UserController {
fun login(loginBody: LoginBody) = transaction {
val query = UsersTable.leftJoin(UserHasTypeTable).select { UsersTable.email eq loginBody.email }
val result = UsersEntity.wrapRows(query).first()
if(loginBody.password == result.password)
return@transaction result.userResponse()
else
null
}
}

UserRoute

fun Route.userRoute(userController: UserController) {
post("login") {
val loginBody = call.receive<LoginBody>()

val db = userController.login(loginBody)
db.let {
call.respond(JsonResponse.success(loginResponse,HttpStatusCode.OK))
}
}
}

Ref:  https://piashcse.medium.com/get-child-data-in-parent-table-by-backreferencedon-in-kotlin-exposed-ktor-part-3-80bb14675871