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

November 23, 2024

Zustand Simplified: Effortless State Management for Favorites in React Native

November 23, 2024 Posted by Piash , , , , No comments

 State management is a cornerstone of any React Native application. While libraries like Redux are popular, sometimes we need something lightweight yet powerful. Enter Zustand, a minimalist state management library that’s perfect for handling state in React Native. In this article, we’ll explore how to use Zustand to manage a favorites list for Movies and TV series in a React Native app.

zustand in react native

Why Use Zustand in React Native?
Zustand is simple, scalable, and performant. Here’s why it’s a great choice for React Native:
1. Minimal Boilerplate: Create stores without cumbersome setup.
2. Middleware Support: Includes built-in middleware for features like persistence.
3. Lightweight: Tiny bundle size and fast performance.
4. Reactivity: Automatically triggers re-renders when state changes.
5. Integration with MMKV Storage: Perfect for persisting data efficiently in React Native.

Setting Up Zustand in React Native
First, ensure you have the required packages installed:

npm install zustand
npm install react-native-mmkv zustand/middleware

Here’s how to set up a Zustand store for managing favorite Movies and TV series:
FavoriteStore.ts

import { create } from 'zustand';
import { MovieDetail } from '../types/MovieDetail.ts';
import { persist, createJSONStorage } from 'zustand/middleware';
import { TvSeriesDetail } from '../types/TvSeriesDetail.ts';
import { zustandMMKVStorage } from './mmkv.ts';

interface FavoriteMoviesTvSeriesStore {
favoriteMovies: MovieDetail[];
toggleFavoriteMovie: (movie: MovieDetail) => void;
isFavoriteMovie: (movieId: number) => boolean;
clearFavoriteMovies: () => void;
favoriteTvSeries: TvSeriesDetail[];
toggleFavoriteTvSeries: (movie: TvSeriesDetail) => void;
isFavoriteTvSeries: (tvSeriesId: number) => boolean;
clearFavoriteTvSeries: () => void;
}

export const useFavoriteStore = create<FavoriteMoviesTvSeriesStore>()(
persist(
(set, get) => ({
favoriteMovies: [],
toggleFavoriteMovie: (movie: MovieDetail) => {
set((state: FavoriteMoviesTvSeriesStore) => {
const isFav = state.favoriteMovies.some(
(favMovie: MovieDetail) => favMovie.id === movie.id
);
return {
favoriteMovies: isFav
? state.favoriteMovies.filter(
(favMovie: MovieDetail) => favMovie.id !== movie.id
) // Remove from favorites
: [...state.favoriteMovies, movie], // Add to favorites
};
});
},
isFavoriteMovie: (movieId: number) =>
get().favoriteMovies.some((movie: MovieDetail) => movie.id === movieId),

clearFavoriteMovies: () => set({ favoriteMovies: [] }),
favoriteTvSeries: [],
toggleFavoriteTvSeries: (tvSeries: TvSeriesDetail) => {
set((state: FavoriteMoviesTvSeriesStore) => {
const isFav = state.favoriteTvSeries.some(
(favTvSeries: TvSeriesDetail) => favTvSeries.id === tvSeries.id
);
return {
favoriteTvSeries: isFav
? state.favoriteTvSeries.filter(
(favTvSeries: TvSeriesDetail) =>
favTvSeries.id !== tvSeries.id
) // Remove from favorites
: [...state.favoriteTvSeries, tvSeries], // Add to favorites
};
});
},
isFavoriteTvSeries: (tvSeriesId: number) =>
get().favoriteTvSeries.some(
(tvSeries: TvSeriesDetail) => tvSeries.id === tvSeriesId
),
clearFavoriteTvSeries: () => set({ favoriteTvSeries: [] }),
}),
{
name: '@favorite',
storage: createJSONStorage(() => zustandMMKVStorage),
}
)
);

MMKV Integration: The zustandMMKVStorage integrates Zustand with react-native-mmkv, a high-performance storage solution. Here’s a simple MMKV wrapper:
MMKV Wrapper (mmkv.ts)

import { MMKV } from 'react-native-mmkv';

const mmkv = new MMKV();

export const zustandMMKVStorage = {
getItem: (name: string) => {
const value = mmkv.getString(name);
return value ? JSON.parse(value) : null;
},
setItem: (name: string, value: any) => {
mmkv.set(name, JSON.stringify(value));
},
removeItem: (name: string) => {
mmkv.delete(name);
},
};

Features Explained

  1. Add/Remove Favorite Movies or TV Series: Use toggleFavoriteMovie and toggleFavoriteTvSeries to add or remove items based on their presence in the list.
    2. Check if an Item is a Favorite: Use isFavoriteMovie or isFavoriteTvSeries to check whether a specific item is in the favorites list.
    3. Clear Favorites: Use clearFavoriteMovies and clearFavoriteTvSeries to reset the lists.
    4. Persistence with MMKV: The store is persisted using MMKV for fast, efficient local storage.

Using the Store in Components

import React, { useState } from 'react';
import { FlatList, View } from 'react-native';
import styles from './FavoriteMovie.style.ts';
import { useFavoriteStore } from '../../../local-store/FavoriteStore.ts';
import FavoriteComponent from '../../../components/favorite/FavoriteComponent.tsx';
import { MovieDetail } from '../../../types/MovieDetail.ts';
import { NavigationProp, useNavigation } from '@react-navigation/native';
import { RootStackParam } from '../../../types/navigation/NavigationTypes.ts';
import ConfirmationAlert from '../../../components/alert-dialog/ConfirmationAlert.tsx';
import { confirmationAlert } from '../../../constant/Dictionary.ts';

type FavoriteMovieNavigationProp = NavigationProp<
RootStackParam,
'MovieDetail'
>;

const FavoriteMovie = () => {
const navigation = useNavigation<FavoriteMovieNavigationProp>();
const { favoriteMovies, toggleFavoriteMovie } = useFavoriteStore();

const [visible, setVisible] = useState(false);
const [movieToRemove, setMovieToRemove] = useState<MovieDetail | null>(null);

const showDialog = (movie: MovieDetail) => {
setMovieToRemove(movie);
setVisible(true);
};

const hideDialog = () => {
setVisible(false);
setMovieToRemove(null);
};

const confirmRemoveFavorite = () => {
if (movieToRemove) {
toggleFavoriteMovie(movieToRemove);
}
hideDialog();
};

const favorite = ({ item }: { item: MovieDetail }) => (
<FavoriteComponent
title={item.title}
poster_path={item.poster_path}
onPress={() => navigation.navigate('MovieDetail', { movieId: item.id })}
onRemove={() => showDialog(item)}
/>
);

return (
<View style={styles.mainView}>
<FlatList
style={styles.flatListStyle}
data={favoriteMovies}
renderItem={favorite}
keyExtractor={(item) => item.id.toString()} // Ensure a unique key for each item
/>
<ConfirmationAlert
visible={visible}
title={confirmationAlert.title}
message={confirmationAlert.message}
onConfirm={confirmRemoveFavorite}
onCancel={hideDialog}
/>
</View>
);
};

export default FavoriteMovie;

GitHub link: https://github.com/piashcse/react-native-movie
Medium: https://piashcse.medium.com/zustand-simplified-effortless-state-management-for-favorites-in-react-native-0b51af8b323a

November 12, 2024

CI/CD for Android: A Comprehensive Guide

November 12, 2024 Posted by Piash , , , , No comments

Continuous Integration and Continuous Delivery (CI/CD) has become essential in Android development, helping developers automate building, testing, and deploying applications. In this article, we’ll create a straightforward CI/CD pipeline for Android using GitHub Actions, focusing on building, testing, and artifact management.

Step 1: Initialize a Workflow File
GitHub Actions workflows are configured in YAML files stored in .github/workflows. In your project, create a file named android-ci.yml with the following content.

name: Android CI/CD

on:
push:
branches:
- master # Run the workflow on pushes to the main branch
- main # Run the workflow on pushes to the main branch
pull_request:
branches:
- master # Run the workflow on pushes to the master branch
- main. # Run the workflow on pushes to the main branch

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up JDK
uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: '17' # Change to the version you need for your project

- name: Cache Gradle files
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ runner.os }}

- name: Build and test
run: ./gradlew build --no-daemon

- name: Run unit tests
run: ./gradlew test --no-daemon

# Upload debug APK as an artifact
- name: Build debug APK
run: ./gradlew assembleDebug --no-daemon

- name: Upload APK
uses: actions/upload-artifact@v3
with:
name: movie-world-apk
path: app/build/outputs/apk/develop/debug/app-develop-debug.apk # Path to your APK

Workflow Explanation

1. Triggers: This workflow runs on pushes and pull requests to main and master branches.

2. Checkout Code: Uses actions/checkout@v2 to retrieve your code.

3. Set Up JDK: Installs the required Java Development Kit (JDK) with actions/setup-java@v2.

4. Cache Gradle Dependencies: Speeds up builds by caching Gradle files, saving dependencies between builds.

5. Build and Test: Runs ./gradlew build to build the project and ./gradlew test to execute unit tests.

6. Assemble Debug APK: Builds a debug APK with assembleDebug.

7. Upload Artifact: Stores the generated APK as an artifact for download and further review.

GitHub link: https://github.com/piashcse/Hilt-MVVM-Compose-Movie/tree/master/.github/workflows

Medium: https://piashcse.medium.com/cd-cd-for-android-a-comprehensive-guide-9c8129387ca8