Build a rich, user-editable profile in FlutterFlow by combining Firestore's Map field for customFields, ChoiceChip components for interest selection, an accent color picker stored in App State, and per-field privacy toggles. Store all customization data in a single profile document to keep reads efficient and the UI reactive.
Why Dynamic Profile Customization Matters
Static profile screens lose users quickly. A dynamic profile that lets people express identity — choosing interests, picking accent colors, adding custom fields — dramatically increases engagement and time-in-app. FlutterFlow's Firestore integration makes this achievable without writing backend code, as long as you structure your data model correctly from the start. The key insight is storing all customization in one Firestore document rather than spreading it across multiple collections.
Prerequisites
- A FlutterFlow project with Firebase/Firestore connected and Authentication enabled
- A Firestore 'users' collection with at least one document per authenticated user
- Basic familiarity with FlutterFlow's widget tree and Action Flows
- FlutterFlow Pro or higher if you intend to export and run custom Dart code locally
Step-by-step guide
Design the Firestore profile document schema
Design the Firestore profile document schema
Open the Firestore panel in FlutterFlow (left sidebar → Firestore icon). Select your 'users' collection and add the following fields to your user document schema: 'displayName' (String), 'avatarUrl' (String), 'bio' (String), 'accentColor' (String, stores hex like '#FF5733'), 'interests' (String Array), 'customFields' (Map, for user-defined key-value pairs), 'privacySettings' (Map, keys match field names, values are booleans), and 'profileComplete' (Double, 0.0-1.0). Using a Map for both customFields and privacySettings means you execute a single Firestore read to get everything, avoiding the multi-read penalty that kills performance on profile screens.
Expected result: Your Firestore schema shows all profile fields in the FlutterFlow schema editor, with correct types and no missing required fields.
Build the avatar upload section with edit overlay
Build the avatar upload section with edit overlay
Create a Stack widget at the top of your profile page. Inside it, place a CircleImage widget bound to the 'avatarUrl' Firestore field. Add a second widget (a semi-transparent Container with a camera Icon) positioned at the bottom-right using an Align widget set to bottomRight. Add an OnTap Action Flow to the overlay Container: Action 1 — Upload Media (Image type, from gallery or camera), Action 2 — Upload File to Firebase Storage (path: 'avatars/{currentUserUid}'), Action 3 — Update Firestore Document (users/{currentUserUid}, set avatarUrl to the returned download URL). Wrap the entire Stack in a GestureDetector so tapping anywhere on the avatar also triggers the flow.
Expected result: Tapping the avatar opens the device image picker; after selection, the avatar updates instantly in the UI and the new URL is saved to Firestore.
Add ChoiceChip interest selector
Add ChoiceChip interest selector
Below the avatar, add a Text widget labeled 'Your Interests'. Create a variable called 'selectedInterests' as a Page State variable of type String List. Add a Wrap widget (found under Layout widgets) — this allows chips to flow across multiple lines. Inside the Wrap, use a Builder component looping over a hard-coded list of interest options (e.g., Tech, Design, Gaming, Travel, Food, Fitness, Music, Books). For each item, place a ChoiceChip widget. Set the chip's 'selected' condition to: selectedInterests contains currentItem. Set the chip's onSelected Action to toggle the value — if selected, remove from list; if not, add to list. On the Save button tap, update the Firestore 'interests' field with the selectedInterests Page State variable.
Expected result: Chips highlight/unhighlight on tap; the selected state persists to Firestore when the user saves.
Implement the accent color picker via Custom Action
Implement the accent color picker via Custom Action
Add a 'Theme Color' row to your settings section with a circular color preview Container. Create a Custom Action named 'showColorPicker'. In FlutterFlow's Custom Code editor, add the flutter_colorpicker package reference in pubspec.yaml, then write the action. The action should show a dialog containing the ColorPicker widget from that package, accept the initial color as a parameter, and return the selected hex string. Call this Custom Action on tap of the color preview Container. On completion, update App State variable 'userAccentColor' (String) with the result, then update Firestore document field 'accentColor'. Apply the App State accent color to key UI elements via conditional styling.
1// Custom Action: showColorPicker2import 'package:flutter_colorpicker/flutter_colorpicker.dart';34Future<String> showColorPicker(5 BuildContext context,6 String initialHex,7) async {8 Color pickedColor = Color(9 int.parse(initialHex.replaceFirst('#', '0xFF')),10 );11 await showDialog(12 context: context,13 builder: (ctx) => AlertDialog(14 title: const Text('Pick accent color'),15 content: SingleChildScrollView(16 child: ColorPicker(17 pickerColor: pickedColor,18 onColorChanged: (c) => pickedColor = c,19 hexInputBar: true,20 ),21 ),22 actions: [23 TextButton(24 onPressed: () => Navigator.of(ctx).pop(),25 child: const Text('Done'),26 ),27 ],28 ),29 );30 return '#${pickedColor.value.toRadixString(16).substring(2).toUpperCase()}';31}Expected result: Tapping the color swatch opens a color picker dialog; choosing a color updates the accent preview and saves to Firestore.
Add per-field privacy toggles
Add per-field privacy toggles
For each profile section (bio, interests, customFields), add a small Toggle widget to the right of the section header. Create a Page State Map variable called 'privacyMap' and initialize it from the Firestore 'privacySettings' Map field on page load. Each Toggle's initial value should read from privacyMap[fieldName]. On toggle change, update the privacyMap entry for that field. On save, write the entire privacyMap back to the Firestore 'privacySettings' Map field in one update operation. On the public-facing profile view page, wrap each section in a Visibility widget that checks: privacySettings[fieldName] == true before rendering.
Expected result: Each field section shows a toggle; flipping it and saving correctly hides or reveals that section on other users' views of the profile.
Build the profile completeness tracker
Build the profile completeness tracker
Add a LinearProgressIndicator widget near the top of the edit profile page. Create a Custom Function named 'calculateProfileComplete' that accepts the profile fields as parameters and returns a Double between 0.0 and 1.0. Assign 1 point each to: displayName not empty, avatarUrl not empty, bio not empty, interests length greater than 0, at least one customField, and accentColor not default. Divide total points by 6. Call this function whenever any field changes and update both the local Page State variable and the Firestore 'profileComplete' field. Display the percentage as a Text widget above the progress bar ('Profile 67% complete').
1// Custom Function: calculateProfileComplete2double calculateProfileComplete(3 String displayName,4 String avatarUrl,5 String bio,6 List<String> interests,7 dynamic customFields,8 String accentColor,9) {10 int score = 0;11 if (displayName.trim().isNotEmpty) score++;12 if (avatarUrl.isNotEmpty) score++;13 if (bio.trim().isNotEmpty) score++;14 if (interests.isNotEmpty) score++;15 if (customFields != null &&16 (customFields as Map).isNotEmpty) score++;17 if (accentColor != '#6366F1') score++;18 return score / 6.0;19}Expected result: The progress bar fills as users complete more fields; the percentage label updates live as they type or make selections.
Complete working example
1// FlutterFlow Custom Functions + Actions for Dynamic Profile Customization2// Add these via FlutterFlow's Custom Code panel34import 'package:flutter/material.dart';5import 'package:flutter_colorpicker/flutter_colorpicker.dart';67// ─── Custom Function: calculateProfileComplete ───────────────────────────────8double calculateProfileComplete(9 String displayName,10 String avatarUrl,11 String bio,12 List<String> interests,13 dynamic customFields,14 String accentColor,15) {16 int score = 0;17 if (displayName.trim().isNotEmpty) score++;18 if (avatarUrl.isNotEmpty) score++;19 if (bio.trim().isNotEmpty) score++;20 if (interests.isNotEmpty) score++;21 if (customFields != null &&22 (customFields as Map).isNotEmpty) score++;23 if (accentColor != '#6366F1') score++;24 return score / 6.0;25}2627// ─── Custom Function: hexToColor ─────────────────────────────────────────────28Color hexToColor(String hex) {29 return Color(int.parse(hex.replaceFirst('#', '0xFF')));30}3132// ─── Custom Function: colorToHex ─────────────────────────────────────────────33String colorToHex(Color color) {34 return '#${color.value.toRadixString(16).substring(2).toUpperCase()}';35}3637// ─── Custom Function: checkContrast ──────────────────────────────────────────38// Returns true if white text is readable on the given background color39bool useWhiteText(String backgroundHex) {40 final color = hexToColor(backgroundHex);41 final luminance = color.computeLuminance();42 return luminance < 0.4;43}4445// ─── Custom Action: showColorPicker ──────────────────────────────────────────46Future<String> showColorPicker(47 BuildContext context,48 String initialHex,49) async {50 Color pickedColor = hexToColor(initialHex);51 await showDialog(52 context: context,53 builder: (ctx) => AlertDialog(54 title: const Text('Pick your accent color'),55 content: SingleChildScrollView(56 child: ColorPicker(57 pickerColor: pickedColor,58 onColorChanged: (c) => pickedColor = c,59 hexInputBar: true,60 enableAlpha: false,61 ),62 ),63 actions: [64 TextButton(65 onPressed: () => Navigator.of(ctx).pop(),66 child: const Text('Done'),67 ),68 ],69 ),70 );71 return colorToHex(pickedColor);72}7374// ─── Custom Function: toggleInterest ─────────────────────────────────────────75List<String> toggleInterest(76 List<String> currentInterests,77 String interest,78) {79 final updated = List<String>.from(currentInterests);80 if (updated.contains(interest)) {81 updated.remove(interest);82 } else {83 updated.add(interest);84 }85 return updated;86}Common mistakes when creating a Dynamic User Profile Customization Feature in FlutterFlow
Why it's a problem: Storing all customization data in separate Firestore sub-documents or collections
How to avoid: Use Map-type fields directly in the user document for customFields and privacySettings. Keep interests as a String Array field on the same document. One document read = all profile data.
Why it's a problem: Initializing ChoiceChip selection state from a hard-coded empty list
How to avoid: In the page's On Page Load action, fetch the user document and set the selectedInterests Page State variable from the Firestore 'interests' array field before the widget tree renders.
Why it's a problem: Writing individual Firestore field updates on every keystroke in text fields
How to avoid: Debounce writes with a 1-2 second delay, or batch all field updates into a single Firestore document update triggered only when the user taps a Save button.
Why it's a problem: Storing the accent color as an integer or Color object in Firestore
How to avoid: Always store colors as hex strings (e.g., '#FF5733'). Convert to Color objects in the app using a Custom Function when needed for rendering.
Best practices
- Store all profile customization data in a single Firestore document to minimize read operations and simplify data binding in FlutterFlow.
- Always validate and sanitize custom field keys — disallow empty strings, special characters, and keys that shadow reserved field names like 'uid' or 'email'.
- Cache the user's accent color in App State immediately on login so the theme applies before the Firestore read completes, preventing a flash of unstyled content.
- Debounce all text field changes — wait at least 800ms after the last keystroke before triggering a Firestore update.
- Use Firestore Security Rules to enforce that users can only write to their own profile document: allow write: if request.auth.uid == userId.
- Show a profile completeness percentage prominently — users with complete profiles engage 40-60% more than those with minimal data filled in.
- Respect privacy toggle settings on all public-facing views by querying the privacySettings Map before rendering each profile section.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm building a dynamic user profile customization screen in FlutterFlow connected to Firestore. My user document has fields: displayName (String), avatarUrl (String), bio (String), accentColor (String hex), interests (String Array), customFields (Map), privacySettings (Map). How should I structure the FlutterFlow Action Flow to read all fields on page load, let users edit them, and write all changes back in a single Firestore update on save?
In my FlutterFlow profile edit page, I have a Wrap widget with ChoiceChips for user interests. The chips should pre-select interests from a Firestore String Array field. I have a Page State variable 'selectedInterests' as a String List. Write me the On Page Load action logic and the chip onSelected action logic in plain English steps I can follow in FlutterFlow's Action Flow editor.
Frequently asked questions
Can I add completely custom fields that users define themselves, not just preset ones?
Yes. Use Firestore's Map field type for 'customFields'. Users can type a field name and value; your Action Flow writes a new key-value pair into that Map using the Update Document action with a Map-type value. Display them in a dynamic ListView bound to the Map's entries.
How do I make the accent color affect the whole app, not just the profile page?
Store the accent color in an App State variable (String) that you populate on login. Then in any widget that should use the accent color, set the color property to a conditional expression that reads from App State. For global theme changes you'll need to use a Custom Action that calls FlutterFlow's theme update mechanism after code export.
What happens if a user uploads a very large avatar image?
Firebase Storage accepts files up to 5TB, but large images slow down profile load times. Before uploading, use FlutterFlow's built-in image compression option in the Upload Media action — set max width/height to 512px. This keeps avatar files under 100KB without visible quality loss.
How do I enforce that only the profile owner can edit their own data?
Add a Firestore Security Rule: allow write: if request.auth != null && request.auth.uid == userId; — where userId is the document ID. This means even if someone manipulates the app, they cannot write to another user's profile document.
Can the privacy toggles hide fields from Firestore queries, or just from the UI?
FlutterFlow privacy toggles only control UI visibility by default. The data still exists in Firestore and is readable by anyone with the document ID unless you enforce it at the Firestore Security Rules level. For true privacy, add a rule that checks the privacySettings Map before allowing reads of specific fields.
How many interests should I offer as ChoiceChip options?
Keep it to 8-15 options. More than 15 overwhelming users and causes usability problems in the Wrap layout on small screens. If you need more, group them into categories with expandable sections, or allow users to type their own interests into a text field that adds to the String Array.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation