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

December 19, 2024

Localization in React Native

December 19, 2024 Posted by Piash , , 1 comment

 Localization is essential for making your React Native app accessible to a global audience. By supporting multiple languages, you ensure users from different regions have a seamless experience. Let’s start step by step.

Multiple language support

Step 1: Install Required Libraries

npm install i18next react-i18next react-native-localize

Step 2: Create Translation Files

Store translations in JSON files for each language. For example:
en.json (English):

{
"welcome": "Welcome",
"greeting": "Hello, {{name}}!"
}

bn.json (Bangali):

{
"welcome": "স্বাগতম",
"greeting": "হ্যালো, {{name}}!"
}

Step 3: Initialize i18next

import i18n from 'i18next';
import en from './locale-json/en.json';
import bn from './locale-json/bn.json';
import { initReactI18next } from 'react-i18next';
import { getLocales } from 'react-native-localize';

// Define the type for supported languages
export type TranslationKeys = keyof typeof en;

// Detect the device language
const getDeviceLanguage = (): string => {
const locales = getLocales();
return locales[0]?.languageTag || 'en';
};

// Initialize i18next
i18n.use(initReactI18next).init({
compatibilityJSON: 'v4',
resources: {
en: { translation: en },
bn: { translation: bn },
},
lng: getDeviceLanguage(),
fallbackLng: 'en',
interpolation: {
escapeValue: false,
},
});

export const i18nLocale = i18n;

Step 4: Wrap your app.js with the provider

import { I18nextProvider } from 'react-i18next';
import i18n from 'i18next';

const App = () => {
return (
<Provider store={store}>
<PaperProvider>
<I18nextProvider i18n={i18n}>
<SafeAreaProvider>
<Navigation />
</SafeAreaProvider>
</I18nextProvider>
</PaperProvider>
</Provider>
);
};

Step 5: Make a hook to make it more useful

import { useMemo } from 'react';
import { i18nLocale } from '../locale/i18nLocale.ts';
import { AppString } from '../locale/AppString.ts';

type AppStringKey = keyof typeof AppString; // Keys of APP_STRING

export const useLocalization = () => {
return useMemo(() => {
const localizedStrings: Record<AppStringKey, string> = {} as Record<
AppStringKey,
string
>;

(Object.keys(AppString) as AppStringKey[]).forEach((key) => {
localizedStrings[key] = i18nLocale.t(AppString[key]);
});

return localizedStrings;
}, []);
};
export const AppString = {
WELCOME: 'Welcome',
GEETINGS: "Hello, {{name}}!"
}

Step 6: Use it in your component

const App = () => {
const localization = useLocalization();

return (
<View>
<Text>{localization.WELCOME}</Text>
</View>
);
};

export default App;

Ref:
GitHub: https://github.com/piashcse/react-native-movie
Medium: https://piashcse.medium.com/localization-in-react-native-a2a48e45cd27

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

October 31, 2024

Room Database in Jetpack Compose: A Step-by-Step Guide for Android Development

October 31, 2024 Posted by Piash , , , , , No comments

 Room Database is part of Android’s Architecture Components and serves as an abstraction layer over SQLite. It simplifies data management by offering a type-safe way to interact with the database and enables compile-time checks of SQL statements.

Key Components of Room Database

1. Entity: Represents a table in your database.

2. DAO (Data Access Object): Contains methods for accessing the database.

3. Database Class: Acts as the main access point to the underlying SQLite database.

Step 1: Add Dependencies

To get started, include the Room dependencies in your libs.versions.toml file:

Include the Room dependencies in your app build.gradle. file:

Include the Room dependencies in your project build.gradle. file:

Step 2: Define an Entity

An Entity represents a table within the database. Define your entity as a data class, with each property corresponding to a column.

In this above code:

• @Entity marks this class as a Room entity.

  • @PrimaryKey denotes the primary key for this entity. Here, autoGenerate automatically assigns a unique ID for each User.

Step 3: Create a DAO

The DAO (Data Access Object) interface contains methods for accessing the database. Use SQL queries to define how data should be inserted, updated, retrieved, or deleted.

Each method is annotated with a specific Room operation (@Insert, @Update, @Delete, @Query) to specify the desired action.

Step 4: Create the Database Class

The Database class should be an abstract class extending RoomDatabase. Use the @Database annotation to specify the entities and database version.

if you have a nested data class inside Entity you have to add TypeConverter

Step 5: Build the Database

To build the database, use Room’s databaseBuilder method. This is typically done in your Application class or a dependency injection setup like Hilt.

Or, with Hilt:

Step 6: Perform Database in ViewModel

Step 7: Perform Database in Jetpack UI

Step 8: Here is the final result

Github: https://github.com/piashcse/Hilt-MVVM-Compose-Movie
Medium: https://piashcse.medium.com/room-database-in-jetpack-compose-a-step-by-step-guide-for-android-development-6c7ae419105a