<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Amina Tariq</title>
    <description>The latest articles on Forem by Amina Tariq (@amina_taariq).</description>
    <link>https://forem.com/amina_taariq</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2873745%2F99ce3717-e9cd-4ef9-b096-7fdea497049e.jpg</url>
      <title>Forem: Amina Tariq</title>
      <link>https://forem.com/amina_taariq</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/amina_taariq"/>
    <language>en</language>
    <item>
      <title>Context vs Redux for Theme Management in React Native: Which One Should You Choose?</title>
      <dc:creator>Amina Tariq</dc:creator>
      <pubDate>Tue, 07 Oct 2025 05:21:06 +0000</pubDate>
      <link>https://forem.com/amina_taariq/context-vs-redux-for-theme-management-in-react-native-which-one-should-you-choose-153h</link>
      <guid>https://forem.com/amina_taariq/context-vs-redux-for-theme-management-in-react-native-which-one-should-you-choose-153h</guid>
      <description>&lt;p&gt;Dark mode is no longer a “nice to have” it’s a user expectation. Whether you’re building a consumer app or an enterprise solution, offering both light and dark themes (with system auto-detection and manual override) significantly improves user experience.&lt;br&gt;
&lt;strong&gt;But here comes the question: how do you manage theme state across your app?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The two most common approaches in React Native are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;React Context&lt;/strong&gt; (with useColorScheme)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redux&lt;/strong&gt; (or another global state library)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s break down both approaches, their pros, cons, and when to use each.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Theme with React Context&lt;/strong&gt;&lt;br&gt;
React Context combined with useColorScheme is the lightweight and native-friendly solution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;theme.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const lightTheme = {
  background: "#FFFFFF",
  text: "#000000",
};

export const darkTheme = {
  background: "#000000",
  text: "#FFFFFF",
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ThemeProvider.tsx&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { createContext, useContext } from "react";
import { useColorScheme } from "react-native";
import { lightTheme, darkTheme } from "./theme";

const ThemeContext = createContext(lightTheme);

export const ThemeProvider = ({ children }: { children: React.ReactNode }) =&amp;gt; {
  const scheme = useColorScheme();
  const theme = scheme === "dark" ? darkTheme : lightTheme;

  return (
    &amp;lt;ThemeContext.Provider value={theme}&amp;gt;{children}&amp;lt;/ThemeContext.Provider&amp;gt;
  );
};

export const useTheme = () =&amp;gt; useContext(ThemeContext);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Usage in a screen&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";
import { View, Text } from "react-native";
import { useTheme } from "../ThemeProvider";

export default function HomeScreen() {
  const theme = useTheme();

  return (
    &amp;lt;View style={{ flex: 1, backgroundColor: theme.background }}&amp;gt;
      &amp;lt;Text style={{ color: theme.text }}&amp;gt;Hello, Context Theme!&amp;lt;/Text&amp;gt;
    &amp;lt;/View&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight&lt;/strong&gt;: No external library required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple&lt;/strong&gt;: Perfect for small-to-medium apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-detect&lt;/strong&gt;: Easily integrates with system settings using useColorScheme.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Drawbacks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Limited scaling&lt;/strong&gt;: Managing overrides (for example, theme plus language plus settings) can get messy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistence required&lt;/strong&gt;: If you want user-selected theme (not just system), you’ll need AsyncStorage or a similar solution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-renders&lt;/strong&gt;: Context can trigger unnecessary re-renders if overused.
__________________________________________________________________________&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Theme with Redux&lt;/strong&gt;&lt;br&gt;
Redux takes a state-first approach, making it a better fit for larger apps where preferences are part of global state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;themeSlice.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { createSlice, PayloadAction } from "@reduxjs/toolkit";

type ThemeState = {
  mode: "light" | "dark" | "system";
};

const initialState: ThemeState = {
  mode: "system",
};

const themeSlice = createSlice({
  name: "theme",
  initialState,
  reducers: {
    setTheme: (state, action: PayloadAction&amp;lt;"light" | "dark" | "system"&amp;gt;) =&amp;gt; {
      state.mode = action.payload;
    },
  },
});

export const { setTheme } = themeSlice.actions;
export default themeSlice.reducer;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;useAppTheme.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useColorScheme } from "react-native";
import { useSelector } from "react-redux";
import { RootState } from "./store";
import { lightTheme, darkTheme } from "./theme";

export const useAppTheme = () =&amp;gt; {
  const systemScheme = useColorScheme(); // 'light' or 'dark'
  const mode = useSelector((state: RootState) =&amp;gt; state.theme.mode);

  const scheme = mode === "system" ? systemScheme : mode;

  return scheme === "dark" ? darkTheme : lightTheme;
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Usage in a screen&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";
import { View, Text, Button } from "react-native";
import { useAppTheme } from "../useAppTheme";
import { useDispatch } from "react-redux";
import { setTheme } from "../themeSlice";

export default function SettingsScreen() {
  const theme = useAppTheme();
  const dispatch = useDispatch();

  return (
    &amp;lt;View style={{ flex: 1, backgroundColor: theme.background }}&amp;gt;
      &amp;lt;Text style={{ color: theme.text }}&amp;gt;Theme Settings&amp;lt;/Text&amp;gt;
    &amp;lt;/View&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Centralized control&lt;/strong&gt;: Everything runs through Redux, making it easy to debug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistence&lt;/strong&gt;: Combine with redux-persist for long-term storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scales better&lt;/strong&gt;: Useful if you’re already using Redux for authentication, settings, or language preferences.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Drawbacks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overhead&lt;/strong&gt;: If you don’t already use Redux, it’s overkill just for theme.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boilerplate&lt;/strong&gt;: Slices, actions, and selectors add more code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extra dependency&lt;/strong&gt;: Adds Redux to your bundle if it’s not already there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Recommendation&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Start simple&lt;/strong&gt;: If your app doesn’t already use Redux, stick with &lt;strong&gt;Context&lt;/strong&gt;. It’s lean, straightforward, and integrates smoothly with system theme detection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Go Redux&lt;/strong&gt;: If your app already uses &lt;strong&gt;Redux&lt;/strong&gt; (or will grow to include global state like user preferences, localization, or complex settings), it makes sense to manage theme inside Redux for consistency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Think of it like this:&lt;/strong&gt;&lt;br&gt;
Context is a screwdriver — small, handy, lightweight.&lt;br&gt;
Redux is a toolbox — heavier, but gives you everything you need for a big project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Theme management in React Native doesn’t have to be complicated. Use Context when you want speed and simplicity. Use Redux when you need scalability, persistence, and centralized control.&lt;/p&gt;

&lt;p&gt;Both are valid approaches; the real decision comes down to the size of your app and the complexity of your state.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>mobile</category>
      <category>redux</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Making Android Bottom Sheets Look and Behave More Like iOS in .NET MAUI</title>
      <dc:creator>Amina Tariq</dc:creator>
      <pubDate>Thu, 26 Jun 2025 19:07:46 +0000</pubDate>
      <link>https://forem.com/amina_taariq/making-android-bottom-sheets-look-and-behave-more-like-ios-in-net-maui-h7j</link>
      <guid>https://forem.com/amina_taariq/making-android-bottom-sheets-look-and-behave-more-like-ios-in-net-maui-h7j</guid>
      <description>&lt;p&gt;Bottom sheets are a popular UI pattern for displaying contextual content without navigating away from the current screen. However, the default implementations on Android and iOS can look and feel quite different, leading to inconsistent user experiences across platforms. In this post, we'll explore how to create a custom bottom sheet implementation that makes Android bottom sheets behave more like their iOS counterparts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Platform Inconsistencies
&lt;/h2&gt;

&lt;p&gt;By default, Android and iOS handle bottom sheets differently:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Android Bottom Sheets:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Often appear without affecting the background content&lt;/li&gt;
&lt;li&gt;May not have consistent backdrop behavior&lt;/li&gt;
&lt;li&gt;Can have different animation styles&lt;/li&gt;
&lt;li&gt;Touch handling varies between implementations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;iOS Bottom Sheets:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consistently dim the background content&lt;/li&gt;
&lt;li&gt;Have smooth, predictable animations&lt;/li&gt;
&lt;li&gt;Provide clear visual separation between the sheet and background&lt;/li&gt;
&lt;li&gt;Offer intuitive gesture-based interactions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Can be dismissed by tapping outside the sheet area&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Solution: Custom Bottom Sheet Implementation
&lt;/h2&gt;

&lt;p&gt;Let's walk through creating a custom bottom sheet that brings iOS-like behavior to Android while maintaining native performance. This implementation is available as a complete project on GitHub: &lt;a href="https://github.com/amina-taariq/CustomBottomSheet" rel="noopener noreferrer"&gt;CustomBottomSheet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The solution provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dimmed background&lt;/strong&gt; for better focus&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proper cleanup&lt;/strong&gt; of active bottom sheets
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cancelable by tapping outside&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Only one bottom sheet active at a time&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent API&lt;/strong&gt; for use in views and view models&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Setting Up the Base Class
&lt;/h3&gt;

&lt;p&gt;We'll extend the existing &lt;code&gt;BottomSheet&lt;/code&gt; from The49.Maui.BottomSheet package to add our custom behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;The49.Maui.BottomSheet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Maui.Controls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;CustomBottomSheet.Custom&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomBottomsheet&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BottomSheet&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;CustomBottomsheet&lt;/span&gt; &lt;span class="n"&gt;CurrentBottomSheet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isInitialized&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isDisposed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;CustomBottomsheet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;InitializeBottomSheet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Managing Bottom Sheet Lifecycle
&lt;/h3&gt;

&lt;p&gt;The key to iOS-like behavior is proper lifecycle management. We need to ensure only one bottom sheet is active at a time and handle the background dimming correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;InitializeBottomSheet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isInitialized&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;isDisposed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Showing&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;OnBottomSheetShowing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shown&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;OnBottomSheetShown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dismissed&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;OnBottomSheetDismissed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;isInitialized&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnBottomSheetShowing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isDisposed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Ensure only one bottom sheet is active&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CurrentBottomSheet&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;CurrentBottomSheet&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;CurrentBottomSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CleanupBottomSheet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;CurrentBottomSheet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Apply iOS-like behavior on Android&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DeviceInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Platform&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;DevicePlatform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Android&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancelable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Enable tap-outside-to-dismiss&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasBackdrop&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// Show backdrop for tap detection&lt;/span&gt;
        &lt;span class="nf"&gt;HandleAndroidOpacityForBottomSheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Enabling iOS-Like Tap-Outside-to-Dismiss
&lt;/h3&gt;

&lt;p&gt;One of the most important iOS behaviors is the ability to dismiss bottom sheets by tapping outside the content area. This provides intuitive interaction that users expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnBottomSheetShowing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isDisposed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Ensure only one bottom sheet is active&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CurrentBottomSheet&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;CurrentBottomSheet&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;CurrentBottomSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CleanupBottomSheet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;CurrentBottomSheet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Apply iOS-like behavior on Android&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DeviceInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Platform&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;DevicePlatform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Android&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancelable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Enable tap-outside-to-dismiss&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasBackdrop&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// Show backdrop for tap detection&lt;/span&gt;
        &lt;span class="nf"&gt;HandleAndroidOpacityForBottomSheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Properties for iOS-Like Behavior:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;IsCancelable = true&lt;/code&gt;&lt;/strong&gt;: Allows the bottom sheet to be dismissed by tapping outside&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;HasBackdrop = true&lt;/code&gt;&lt;/strong&gt;: Creates a backdrop area that can detect tap gestures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background dimming&lt;/strong&gt;: Visual feedback that the sheet is modal and can be dismissed&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4: The Magic - Background Dimming
&lt;/h3&gt;

&lt;p&gt;The most noticeable difference between platforms is how the background is handled. iOS consistently dims the background content, creating a clear focus on the bottom sheet. Here's how we achieve this on Android:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;HandleAndroidOpacityForBottomSheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if ANDROID
&lt;/span&gt;    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;currentPageView&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Shell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;CurrentPage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentPageView&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;currentPageView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Opacity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Fallback for non-Shell applications&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;currentPageOutsideShell&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MainPage&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Navigation&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;NavigationStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LastOrDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentPageOutsideShell&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;currentPageOutsideShell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Opacity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dims the background&lt;/strong&gt; by reducing the current page's opacity to 0.8 (you can adjust this value)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works with Shell and non-Shell applications&lt;/strong&gt; by checking both navigation patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only affects Android&lt;/strong&gt; using conditional compilation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creates visual hierarchy&lt;/strong&gt; similar to iOS modal presentations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 5: Proper Cleanup and State Management
&lt;/h3&gt;

&lt;p&gt;To prevent memory leaks and ensure smooth transitions, we need comprehensive cleanup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnBottomSheetDismissed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DismissOrigin&lt;/span&gt; &lt;span class="n"&gt;dismissOrigin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;CleanupBottomSheet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;CleanupBottomSheet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isDisposed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Restore background opacity&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DeviceInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Platform&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;DevicePlatform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Android&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;HandleAndroidOpacityForBottomSheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Clear current reference&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CurrentBottomSheet&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;CurrentBottomSheet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;ResetBottomSheetState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ResetBottomSheetState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;isDisposed&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;DeviceInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Platform&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;DevicePlatform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Android&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancelable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// Maintain tap-outside-to-dismiss&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasBackdrop&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// Keep backdrop for consistent behavior&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Memory Management
&lt;/h3&gt;

&lt;p&gt;Implement proper disposal to prevent memory leaks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ManualCleanup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isDisposed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;CleanupBottomSheet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;DisposeBottomSheet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;DisposeBottomSheet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isDisposed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isInitialized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Showing&lt;/span&gt; &lt;span class="p"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;OnBottomSheetShowing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shown&lt;/span&gt; &lt;span class="p"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;OnBottomSheetShown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dismissed&lt;/span&gt; &lt;span class="p"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;OnBottomSheetDismissed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;CleanupBottomSheet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;isDisposed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;isInitialized&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="nf"&gt;CustomBottomsheet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;DisposeBottomSheet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;You can find the complete implementation in the &lt;a href="https://github.com/amina-taariq/CustomBottomSheet" rel="noopener noreferrer"&gt;CustomBottomSheet GitHub repository&lt;/a&gt;. The repository includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete source code with detailed comments&lt;/li&gt;
&lt;li&gt;Example usage scenarios&lt;/li&gt;
&lt;li&gt;Setup instructions for .NET MAUI projects&lt;/li&gt;
&lt;li&gt;Integration with The49.Maui.BottomSheet package&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before implementing this solution, ensure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The49.Maui.BottomSheet NuGet package installed&lt;/li&gt;
&lt;li&gt;.NET MAUI project setup&lt;/li&gt;
&lt;li&gt;Understanding of MAUI lifecycle management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Benefits
&lt;/h2&gt;

&lt;p&gt;This implementation provides several advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Consistent Visual Experience&lt;/strong&gt;: Android bottom sheets now dim the background like iOS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intuitive Interaction&lt;/strong&gt;: Tap outside to dismiss, just like iOS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proper State Management&lt;/strong&gt;: Only one bottom sheet active at a time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory Efficiency&lt;/strong&gt;: Proper cleanup prevents memory leaks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-Platform Compatibility&lt;/strong&gt;: Works with both Shell and traditional navigation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smooth Animations&lt;/strong&gt;: Background opacity changes create smooth transitions&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;p&gt;When implementing this solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test on both platforms&lt;/strong&gt; to ensure the behavior feels natural&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adjust opacity values&lt;/strong&gt; based on your app's design requirements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consider accessibility&lt;/strong&gt; when dimming background content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle edge cases&lt;/strong&gt; like rapid successive bottom sheet presentations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor memory usage&lt;/strong&gt; in long-running applications&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By implementing this custom bottom sheet solution, you can achieve iOS-like consistency across both platforms while maintaining the native performance and feel that users expect. The background dimming effect, combined with proper lifecycle management and tap-outside-to-dismiss functionality, creates a polished user experience that feels cohesive regardless of the platform.&lt;/p&gt;

&lt;p&gt;The key insight is that small visual details like background dimming and intuitive interactions can significantly impact the perceived quality and consistency of your cross-platform application. With this implementation, your Android users will enjoy the same polished bottom sheet experience as your iOS users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;The complete implementation is available on GitHub: &lt;a href="https://github.com/amina-taariq/CustomBottomSheet" rel="noopener noreferrer"&gt;amina-taariq/CustomBottomSheet&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This repository provides a reusable solution that you can easily integrate into your .NET MAUI projects to achieve consistent cross-platform bottom sheet behavior.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Want to learn more about cross-platform UI consistency in .NET MAUI? Follow along for more tips and techniques for creating seamless user experiences across Android and iOS.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Make CarouselView Work with Pinch-to-Zoom in .NET MAUI</title>
      <dc:creator>Amina Tariq</dc:creator>
      <pubDate>Sun, 15 Jun 2025 09:47:37 +0000</pubDate>
      <link>https://forem.com/amina_taariq/how-to-make-carouselview-work-with-pinch-to-zoom-in-net-maui-5aao</link>
      <guid>https://forem.com/amina_taariq/how-to-make-carouselview-work-with-pinch-to-zoom-in-net-maui-5aao</guid>
      <description>&lt;p&gt;The CarouselView in .NET MAUI is a powerful control for building swipeable collections, perfect for galleries, tutorials, or sliders. But what if you want zoomable content—like images—inside each Carousel item?&lt;/p&gt;

&lt;p&gt;This post walks you through combining CarouselView with a custom PinchToZoomContainer, enabling pinch-to-zoom and pan interactions inside carousel slides without breaking swipe navigation. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Challenge&lt;/strong&gt;&lt;br&gt;
When you place a zoomable view inside a CarouselView, gesture conflicts arise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Swiping to the next item vs. panning the zoomed content&lt;/li&gt;
&lt;li&gt;Double-tap or pinch interfering with CarouselView's native swipe&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If not handled carefully, users will either fail to zoom, or accidentally change slides while zooming. Our goal: smooth gestures without compromise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Building Block: PinchToZoomContainer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We use a custom ContentView (PinchToZoomContainer) that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handles pinch-to-zoom and double-tap&lt;/li&gt;
&lt;li&gt;Allows panning only when zoomed in&lt;/li&gt;
&lt;li&gt;Resets to original scale when zoomed out&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a quick recap of its structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class PinchToZoomContainer : ContentView
{
    // Handles pinch, pan, and double-tap gestures
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It wraps your content (e.g., an image) and manages gestures smartly.&lt;br&gt;
 Integrating with CarouselView&lt;br&gt;
Now, embed the PinchToZoomContainer in each carousel item:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;CarouselView ItemsSource="{Binding Images}"&amp;gt;
    &amp;lt;CarouselView.ItemTemplate&amp;gt;
        &amp;lt;DataTemplate&amp;gt;
            &amp;lt;controls:PinchToZoomContainer&amp;gt;
                &amp;lt;Image Source="{Binding}" Aspect="AspectFit" /&amp;gt;
            &amp;lt;/controls:PinchToZoomContainer&amp;gt;
        &amp;lt;/DataTemplate&amp;gt;
    &amp;lt;/CarouselView.ItemTemplate&amp;gt;
&amp;lt;/CarouselView&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔥 Key Idea&lt;/strong&gt;: The zoom container manages gestures at the item level, allowing CarouselView to handle swipes only when the content is not zoomed in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gesture Conflict Handling&lt;/strong&gt;&lt;br&gt;
The magic happens in your zoom container’s pan gesture logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (Content.Scale &amp;lt;= 1)
{
    return; // Let CarouselView handle the swipe
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line ensures that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zoomed out: CarouselView handles swipe&lt;/li&gt;
&lt;li&gt;Zoomed in: Only the content pans, swipe is blocked&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, we dynamically add or remove pan gestures based on zoom state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (_currentScale &amp;gt; 1 &amp;amp;&amp;amp; !GestureRecognizers.Contains(_panGesture))
{
    GestureRecognizers.Add(_panGesture);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps interactions natural and intuitive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full Example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s a complete snippet&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;CarouselView ItemsSource="{Binding ImageSources}"&amp;gt;
    &amp;lt;CarouselView.ItemTemplate&amp;gt;
        &amp;lt;DataTemplate&amp;gt;
            &amp;lt;controls:PinchToZoomContainer&amp;gt;
                &amp;lt;Image Source="{Binding}" Aspect="AspectFit" /&amp;gt;
            &amp;lt;/controls:PinchToZoomContainer&amp;gt;
        &amp;lt;/DataTemplate&amp;gt;
    &amp;lt;/CarouselView.ItemTemplate&amp;gt;
&amp;lt;/CarouselView&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the ViewModel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public ObservableCollection&amp;lt;string&amp;gt; ImageSources { get; } = new()
{
    "image1.jpg",
    "image2.jpg",
    "image3.jpg"
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What Works Well&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Pinch-to-zoom works seamlessly inside each slide&lt;/li&gt;
&lt;li&gt;✅ Swipe between slides only when not zoomed in&lt;/li&gt;
&lt;li&gt;✅ Double-tap toggles zoom&lt;/li&gt;
&lt;li&gt;✅ Panning stays within bounds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Wrap Up&lt;/strong&gt;&lt;br&gt;
You don’t have to choose between pinch-to-zoom and carousel swiping—you can have both! By isolating gestures within a PinchToZoomContainer and being smart about when panning is allowed, your users can enjoy a smooth, professional zoom gallery experience in .NET MAUI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check out the GitHub repo:&lt;/strong&gt; &lt;a href="//git@github.com:amina-taariq/PinchToZoomCarouselApp.git"&gt;PinchToZoomCarouselApp&lt;/a&gt; &lt;/p&gt;

</description>
      <category>maui</category>
      <category>dotnet</category>
      <category>mobile</category>
      <category>programming</category>
    </item>
    <item>
      <title>Enhance MAUI Entry: Auto-Format Credit Card Numbers!</title>
      <dc:creator>Amina Tariq</dc:creator>
      <pubDate>Tue, 18 Mar 2025 07:03:09 +0000</pubDate>
      <link>https://forem.com/amina_taariq/enhance-your-maui-entry-auto-format-credit-card-numbers-33dc</link>
      <guid>https://forem.com/amina_taariq/enhance-your-maui-entry-auto-format-credit-card-numbers-33dc</guid>
      <description>&lt;p&gt;&lt;strong&gt;🚀 Seamless UX with Auto-Formatting 🚀&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Have you ever struggled with users entering messy credit card numbers? Say no more! Implement a custom MAUI Behavior that formats credit card numbers dynamically as users type. 🏦💳&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;✅ Automatically insert spaces every 4 digits (e.g., 1234 5678 9012 3456).&lt;br&gt;
✅ Maintains correct cursor position while typing or deleting.&lt;br&gt;
✅ Removes non-numeric characters automatically.&lt;br&gt;
✅ Enhances user experience with clean and readable input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Implementation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;`public class CardNumberFormatterBehavior : Behavior&lt;br&gt;
{&lt;br&gt;
    private bool _isUpdating = false;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protected override void OnAttachedTo(Entry entry)
{
    entry.TextChanged += OnEntryTextChanged;
    base.OnAttachedTo(entry);
}

protected override void OnDetachingFrom(Entry entry)
{
    entry.TextChanged -= OnEntryTextChanged;
    base.OnDetachingFrom(entry);
}

private void OnEntryTextChanged(object sender, TextChangedEventArgs args)
{
    if (_isUpdating || !(sender is Entry entry))
        return;

    _isUpdating = true;

    try
    {
        int cursorPosition = entry.CursorPosition;

        if (string.IsNullOrEmpty(args.NewTextValue))
        {
            entry.Text = string.Empty;
            entry.CursorPosition = 0;
            return;
        }

        int oldSpacesBeforeCursor = 0;
        if (!string.IsNullOrEmpty(args.OldTextValue) &amp;amp;&amp;amp; cursorPosition &amp;lt;= args.OldTextValue.Length)
        {
            oldSpacesBeforeCursor = args.OldTextValue.Substring(0, cursorPosition).Count(c =&amp;gt; c == ' ');
        }

        int oldDigitsBeforeCursor = cursorPosition - oldSpacesBeforeCursor;

        string cleanText = new string(args.NewTextValue.Where(char.IsDigit).ToArray());

        string formattedText = FormatCardNumber(cleanText);

        entry.Text = formattedText;

        int newCursorPosition;

        if (args.NewTextValue.Length &amp;gt; args.OldTextValue?.Length)
        {
            int spacesInFormattedTextUpToDigits = 0;
            int digitCount = 0;

            for (int i = 0; i &amp;lt; formattedText.Length &amp;amp;&amp;amp; digitCount &amp;lt; oldDigitsBeforeCursor + 1; i++)
            {
                if (formattedText[i] == ' ')
                {
                    spacesInFormattedTextUpToDigits++;
                }
                else
                {
                    digitCount++;
                }
            }

            newCursorPosition = oldDigitsBeforeCursor + spacesInFormattedTextUpToDigits + 1;
        }
        else
        {
            int spacesInFormattedTextUpToDigits = 0;
            int digitCount = 0;

            for (int i = 0; i &amp;lt; formattedText.Length &amp;amp;&amp;amp; digitCount &amp;lt; oldDigitsBeforeCursor; i++)
            {
                if (formattedText[i] == ' ')
                {
                    spacesInFormattedTextUpToDigits++;
                }
                else
                {
                    digitCount++;
                }
            }

            newCursorPosition = oldDigitsBeforeCursor + spacesInFormattedTextUpToDigits;
        }

        newCursorPosition = Math.Max(0, Math.Min(formattedText.Length, newCursorPosition));
        entry.CursorPosition = newCursorPosition;
    }
    finally
    {
        _isUpdating = false;
    }
}

private string FormatCardNumber(string cardNumber)
{
    if (string.IsNullOrEmpty(cardNumber))
        return string.Empty;

    StringBuilder result = new StringBuilder();

    for (int i = 0; i &amp;lt; cardNumber.Length; i++)
    {
        if (i &amp;gt; 0 &amp;amp;&amp;amp; i % 4 == 0)
            result.Append(" ");

        result.Append(cardNumber[i]);
    }

    return result.ToString();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}`&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Use It in Your MAUI App&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Simply attach the behavior to an Entry in XAML:&lt;br&gt;
&lt;code&gt;&amp;lt;Entry Placeholder="Enter card number"&amp;gt;&lt;br&gt;
    &amp;lt;Entry.Behaviors&amp;gt;&lt;br&gt;
        &amp;lt;local:CardNumberFormatterBehavior /&amp;gt;&lt;br&gt;
    &amp;lt;/Entry.Behaviors&amp;gt;&lt;br&gt;
&amp;lt;/Entry&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feaizmis73joturo1a6f4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feaizmis73joturo1a6f4.jpg" alt="output" width="720" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Use This?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;🚀 Improves data consistency.&lt;br&gt;
🎯 Reduces user input errors.&lt;br&gt;
🔧 Works seamlessly in .NET MAUI apps.&lt;/p&gt;

&lt;p&gt;Would you add any improvements to this behavior? Drop your thoughts below! ⬇️💬&lt;/p&gt;

</description>
      <category>maui</category>
      <category>csharp</category>
      <category>mobile</category>
      <category>dotnet</category>
    </item>
  </channel>
</rss>
