Build a device customization app in FlutterFlow using Custom Code and platform-specific packages. Android supports wallpaper changes via wallpaper_manager, sound profile control, and layout tools. iOS restricts system-level customization by design — disclose this upfront in your app UI and scope your features to in-app theming that works on both platforms.
What Is Possible (and What Is Not) on Each Platform
Device customization apps are among the most requested Flutter projects. The reality is that Android and iOS have fundamentally different security models: Android allows third-party apps to change system wallpapers, manage sound profiles, and in some versions interact with the home screen launcher. iOS, by design, does not expose these system APIs to third-party apps at all — there is no public API to change wallpapers, install custom launchers, or change system sound profiles. Knowing this upfront saves hours of debugging. This tutorial builds a genuinely useful customization app that does what is possible on both platforms (in-app theming, layout saving, color schemes) and clearly communicates the Android-only features in your UI rather than hiding the limitation.
Prerequisites
- A FlutterFlow project on the Pro plan with code export enabled
- Android Studio or Xcode for testing — Run Mode web preview does not support system APIs
- Basic understanding of FlutterFlow Custom Widgets and Custom Actions
- A Firestore collection called user_preferences to persist customization settings
Step-by-step guide
Disclose platform limitations with a first-launch banner
Disclose platform limitations with a first-launch banner
The most important step in building a device customization app is honest platform communication. Create a Component called PlatformNotice. Use a Conditional widget that checks the Platform (via a Custom Function returning 'android' or 'ios') and shows different content for each. On iOS, display a clearly styled info banner at the top of every page that lists system-level features as 'Android only'. Label each feature chip with a platform badge: a green Android chip for wallpaper and sound profiles, a universal chip for theme and color customization. This prevents iOS users from spending time trying to access features that will never work on their device, and significantly reduces negative app store reviews. Wire the Platform detection Custom Function to an App State variable set on first load.
1// Custom Function: getPlatformName2import 'dart:io';34String getPlatformName() {5 if (Platform.isAndroid) return 'android';6 if (Platform.isIOS) return 'ios';7 return 'other';8}Expected result: On Android, all feature sections display normally. On iOS, system-level sections show a clear 'Android only' badge and are visually disabled.
Add wallpaper_manager and build the wallpaper picker (Android only)
Add wallpaper_manager and build the wallpaper picker (Android only)
Add wallpaper_manager: ^2.0.1 to your pubspec dependencies in FlutterFlow Settings. Create a Custom Action called setWallpaper. Check Platform.isAndroid at the top — if false, return immediately with an error message. Use image_picker to let the user select a photo from their gallery, then call WallpaperManager.setWallpaper(fileBytes, WallpaperManager.HOME_SCREEN) to apply it. Wrap the call in a try-catch and surface any SecurityException errors as user-friendly messages. In FlutterFlow, create a WallpaperPage with a GridView of preset wallpaper thumbnails stored in Firebase Storage, plus a 'Choose from Gallery' button. Each preset tile calls setWallpaper with the remote image URL downloaded to a temp file first.
1import 'dart:io';2import 'package:wallpaper_manager/wallpaper_manager.dart';3import 'package:image_picker/image_picker.dart';4import 'package:http/http.dart' as http;5import 'package:path_provider/path_provider.dart';67Future<String> setWallpaperFromUrl(String imageUrl, String location) async {8 if (!Platform.isAndroid) return 'Wallpaper changes are Android-only.';910 try {11 final response = await http.get(Uri.parse(imageUrl));12 final dir = await getTemporaryDirectory();13 final file = File('${dir.path}/wallpaper_tmp.jpg');14 await file.writeAsBytes(response.bodyBytes);1516 final loc = location == 'home'17 ? WallpaperManager.HOME_SCREEN18 : WallpaperManager.LOCK_SCREEN;1920 final result = await WallpaperManager.setWallpaperFromFile(file.path, loc);21 return result == true ? 'Wallpaper set successfully' : 'Failed to set wallpaper';22 } catch (e) {23 return 'Error: $e';24 }25}Expected result: On Android, selecting a preset wallpaper tile applies it to the home screen within 2-3 seconds. On iOS, the button shows 'Android only' and is non-interactive.
Build the drag-and-drop icon layout builder
Build the drag-and-drop icon layout builder
Create a LayoutBuilderPage with a GridView widget in a fixed 4-column layout. Populate the grid with app shortcut icons the user can save — these are not real system shortcuts but rather a visual layout planner that helps users plan their home screen arrangement before manually doing it. Use FlutterFlow's ReorderableListView or a Custom Widget wrapping Flutter's Draggable and DragTarget for drag-to-reorder within the grid. Each icon card shows an app icon (from a pre-defined list of common app icons stored as assets), an app name, and a drag handle. When the user reorders the grid, save the new order as a JSON array to their user_preferences Firestore document. On iOS, label this section 'Layout Planner (Visual Only)' since actual home screen placement is controlled by iOS.
Expected result: Users can drag app icon cards to rearrange them in the grid. The layout persists after closing and reopening the app, loaded from Firestore on page init.
Create the color-picker theme builder with live preview
Create the color-picker theme builder with live preview
Create a ThemeBuilderPage with a Column containing three sections: Primary Color, Accent Color, and Background Color. For each section, add a Custom Widget wrapping the flutter_colorpicker package's ColorPicker widget. Wire each picker to a Page State color variable. Add a live preview area below the pickers showing a mock card, a mock button, and a mock text field rendered using the currently selected colors. Add a 'Save Theme' button that writes the three hex color strings to the user's Firestore user_preferences document. On app start, load these values and apply them via FlutterFlow's Theme Colors feature — set the theme colors from the saved Firestore values using a Custom Action that calls ThemeNotifier or equivalent. This works identically on iOS and Android.
1// Save theme to Firestore2import 'package:cloud_firestore/cloud_firestore.dart';3import 'package:firebase_auth/firebase_auth.dart';45Future<void> saveUserTheme({6 required String primaryHex,7 required String accentHex,8 required String backgroundHex,9}) async {10 final uid = FirebaseAuth.instance.currentUser?.uid;11 if (uid == null) return;1213 await FirebaseFirestore.instance14 .collection('user_preferences')15 .doc(uid)16 .set({17 'theme': {18 'primary': primaryHex,19 'accent': accentHex,20 'background': backgroundHex,21 'updatedAt': FieldValue.serverTimestamp(),22 }23 }, SetOptions(merge: true));24}Expected result: Moving the color picker sliders immediately updates the live preview. Tapping 'Save Theme' persists the colors. On next app launch, the saved theme loads automatically.
Add the sound profile manager (Android only)
Add the sound profile manager (Android only)
Create a SoundProfilesPage. On Android, you can control the device's ringer volume and notification volume using the volume_controller package. Add volume_controller: ^2.0.9 to your pubspec dependencies. Create a Custom Action called setVolumeProfile that accepts a profile name ('Silent', 'Vibrate', 'Normal') and maps it to the appropriate volume_controller calls (mute, set to 0.0 with haptics, or set to 0.8). Display three large profile selection cards on the page, each with an icon and label. Highlight the currently active profile. Add a Custom Slider for fine-tuning media volume as well. On iOS, show the section with a clear notice: 'Sound profile control is restricted on iOS by Apple. You can adjust volume using the physical buttons on your device.'
Expected result: On Android, tapping 'Silent' mutes the device immediately. The active profile card highlights. On iOS, the section shows the informational notice.
Complete working example
1import 'dart:io';2import 'package:cloud_firestore/cloud_firestore.dart';3import 'package:firebase_auth/firebase_auth.dart';4import 'package:volume_controller/volume_controller.dart';56// --- Platform detection ---7String getPlatformName() {8 if (Platform.isAndroid) return 'android';9 if (Platform.isIOS) return 'ios';10 return 'other';11}1213// --- Save icon layout order to Firestore ---14Future<void> saveLayoutOrder(List<String> orderedAppIds) async {15 final uid = FirebaseAuth.instance.currentUser?.uid;16 if (uid == null) return;1718 await FirebaseFirestore.instance19 .collection('user_preferences')20 .doc(uid)21 .set({'layoutOrder': orderedAppIds}, SetOptions(merge: true));22}2324// --- Load layout order from Firestore ---25Future<List<String>> loadLayoutOrder() async {26 final uid = FirebaseAuth.instance.currentUser?.uid;27 if (uid == null) return [];2829 final doc = await FirebaseFirestore.instance30 .collection('user_preferences')31 .doc(uid)32 .get();3334 final data = doc.data();35 if (data == null || data['layoutOrder'] == null) return [];36 return List<String>.from(data['layoutOrder']);37}3839// --- Save color theme ---40Future<void> saveUserTheme({41 required String primaryHex,42 required String accentHex,43 required String backgroundHex,44}) async {45 final uid = FirebaseAuth.instance.currentUser?.uid;46 if (uid == null) return;4748 await FirebaseFirestore.instance49 .collection('user_preferences')50 .doc(uid)51 .set({52 'theme': {53 'primary': primaryHex,54 'accent': accentHex,55 'background': backgroundHex,56 }57 }, SetOptions(merge: true));58}5960// --- Android-only: set volume profile ---61Future<String> setVolumeProfile(String profile) async {62 if (!Platform.isAndroid) return 'Sound profiles are Android-only.';6364 switch (profile) {65 case 'Silent':66 await VolumeController().setVolume(0.0, showSystemUI: false);67 await VolumeController().muteVolume(showSystemUI: false);68 break;69 case 'Vibrate':70 await VolumeController().setVolume(0.0, showSystemUI: false);71 break;72 case 'Normal':73 await VolumeController().setVolume(0.8, showSystemUI: false);74 break;75 }76 return '$profile profile applied';77}Common mistakes when building a Mobile Device Customization Tool in FlutterFlow
Why it's a problem: Building iOS device customization features without disclosing iOS API restrictions
How to avoid: Detect the platform on first load and show a clear notice listing which features are Android-only. Disable those feature sections on iOS with a visual badge explanation rather than showing broken or non-functional UI.
Why it's a problem: Testing device customization features in FlutterFlow's Run Mode web preview
How to avoid: Export the project and test on a physical device or emulator. Use the local run command in Android Studio or Xcode to install on a connected device.
Why it's a problem: Not requesting storage or media permissions before accessing wallpaper or gallery features
How to avoid: Use the permission_handler package to check and request necessary permissions before calling image picker or wallpaper APIs. Show a rationale dialog explaining why the permission is needed.
Best practices
- Always check Platform.isAndroid before calling any Android-specific system API
- Show platform badges on every feature — green for cross-platform, orange for Android-only — so users understand scope immediately
- Persist all user customization settings to Firestore so they survive app reinstalls and sync across devices
- Request permissions at the moment they are needed with a contextual explanation, not at app launch
- Test all platform-specific features on physical hardware before publishing — emulators may behave differently
- Provide graceful fallbacks for iOS — offer in-app theming as an alternative when system customization is blocked
- Keep a 'Reset to Defaults' option so users can undo all customizations easily
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I am building a Flutter device customization app. Write a Dart Custom Action that checks the platform, and on Android uses the wallpaper_manager package to set a wallpaper from a URL by downloading it to a temporary file first. On iOS, return a descriptive error message explaining the limitation.
In my FlutterFlow project, create a Custom Action called setVolumeProfile that accepts a String parameter ('Silent', 'Vibrate', 'Normal'). On Android, use the volume_controller package to apply the corresponding volume settings. On iOS, return the message 'Sound profiles are Android-only — use your device buttons to adjust volume.'
Frequently asked questions
Can I change the wallpaper on an iPhone using a FlutterFlow app?
No. Apple does not provide a public API for third-party apps to change the system wallpaper on iOS. Any attempt to do so will fail. Your app can offer in-app background themes and color customization, but cannot touch system-level iOS settings.
Which Android versions support wallpaper changes via wallpaper_manager?
The wallpaper_manager package supports Android 5.0 (API 21) and higher, which covers virtually all active Android devices. However, some Android manufacturers (particularly Samsung on One UI) may restrict wallpaper changes to their own apps. Always test on multiple device brands.
Why do my platform-specific features not work in FlutterFlow's Run Mode?
Run Mode renders your app in a web browser, which does not have access to Android or iOS system APIs. Platform channels, native packages like wallpaper_manager, and volume_controller all require a real native environment. Export your project and test on a physical device or Android emulator.
Can I build a custom Android launcher in FlutterFlow?
Building a fully functional Android launcher requires setting the android.intent.category.HOME and android.intent.category.DEFAULT intent filters in the AndroidManifest.xml file. This is possible with code export and manual AndroidManifest edits, but is a complex project that goes beyond typical FlutterFlow use cases. It is not supported in the visual builder alone.
How do I save the user's customization settings so they persist after reinstalling the app?
Save all settings — colors, layout order, sound profiles — to a Firestore document keyed by the user's UID under a user_preferences collection. On app load, read this document and apply the settings. Since the data is in Firestore, it persists across devices and reinstalls automatically.
Do I need special App Store permissions to change volume programmatically?
On Android, controlling ringer volume does not require a special manifest permission for basic media volume. Accessing Do Not Disturb mode requires the ACCESS_NOTIFICATION_POLICY permission. On iOS, third-party apps cannot programmatically change system volume at all — users must use hardware buttons.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation