dynamic_popup 1.0.8
dynamic_popup: ^1.0.8 copied to clipboard
A flexible and customizable dynamic popup system for Flutter with markdown support and interactive components.
Dynamic Popup #
A flexible and customizable dynamic popup system for Flutter with markdown support and interactive components.
๐ Table of Contents #
- Features
- Installation
- Popup Types
- Supported Components
- Placeholder Syntax
- Usage
- Conditional Logic
- Data Usage (Optional)
- Support
Features #
- Configurable popups from backend APIs
- Markdown parser with support for dynamic component placeholders
- Interactive components: RadioButton, Checkbox, TextArea, TextField, Dropdown
- Automatic validation of required fields
- Persistent state management (show once, completed/dismissed)
- Blocking and non-blocking popup behaviors
- Easy to customize and extend
- No dependency on GetX - works with any Flutter app
- Minimal API requirements - only two endpoints required
- Custom slots support - add custom titles, footers, and action buttons
- Smart scrolling - automatically shows scroll buttons for long content
- Advanced conditional logic - show/hide fields or change required status based on other field values with three-tier priority system
- Dynamic required indicators - real-time asterisk updates based on conditional logic
๐ Installation #
Add this to your package's pubspec.yaml file:
dependencies:
dynamic_popup: ^1.0.7
Then run:
flutter pub get
Note: The http package is only required if you use the example implementations. The core library does not depend on http.
๐ฑ Popup Types #
1. Non-blocking Popup #
- User can close without completing
- Ideal for information, optional surveys, feature discovery
2. Blocking Popup #
- User must complete to continue
- Ideal for terms of service, privacy policy, required data
๐ Supported Components #
RadioButton #
<!-- New HTML-like syntax with custom initiator -->
:::dc<radiobutton id="component_id" required label="Label">
<option id="opt1">Option1</option>
<option id="opt2">Option2</option>
<option id="opt3">Option3</option>
</radiobutton>dc:::
Checkbox (single/multiple) #
<!-- New HTML-like syntax with custom initiator -->
:::dc<checkbox id="component_id" label="Label">
<option id="opt1">Option1</option>
<option id="opt2">Option2</option>
<option id="opt3">Option3</option>
</checkbox>dc:::
TextArea #
<!-- New HTML-like syntax with custom initiator -->
:::dc<textarea id="component_id" required label="Label" placeholder="Placeholder text" />dc:::
TextField #
<!-- New HTML-like syntax with custom initiator -->
:::dc<textfield id="component_id" label="Label" placeholder="Placeholder text" />dc:::
Dropdown #
<!-- New HTML-like syntax with custom initiator -->
:::dc<dropdown id="component_id" required label="Label">
<option id="opt1">Option1</option>
<option id="opt2">Option2</option>
<option id="opt3">Option3</option>
</dropdown>dc:::
๐ง Placeholder Syntax #
New Syntax (HTML-like with Named Attributes and Custom Initiator) #
:::dc<component_type id="component_id" required label="Label" placeholder="Placeholder" />dc:::
Custom Initiator: :::dc< and >dc::: - This prevents conflicts with regular HTML tags in markdown content.
Multiline Support: The syntax supports newlines and spaces between the initiator and terminator:
:::dc
<component_type id="component_id" required label="Label">
<option id="opt1">Option1</option>
</component_type>
dc:::
Attributes:
id: Unique component ID (required)required: Presence indicates the field is required (optional)label: Label shown to user (required)placeholder: Placeholder text for text inputs (optional)default: Default value (optional)
Container Components with Option IDs: For components with options (RadioButton, Checkbox, Dropdown), use container syntax with option IDs:
:::dc<component_type id="component_id" required label="Label">
<option id="option_id_1">Option 1</option>
<option id="option_id_2">Option 2</option>
</component_type>dc:::
Benefits of Option IDs:
- Send option IDs instead of text to the backend
- Language-independent option identification
- Easier to maintain when option text changes
๐ป Usage #
Setup Service #
import 'package:dynamic_popup/dynamic_popup.dart';
// Create a repository that implements your API calls
class MyPopupRepository extends BaseDynamicPopupRepository {
@override
Future<PopupApiResponse?> checkForPopup({
required String screenName,
String? userId,
}) async {
// Implement your API call here
// Only these two methods are required
}
@override
Future<bool> submitPopupResponse({
required PopupResponse popupResponse,
}) async {
// Implement your API call here
// Only these two methods are required
}
}
// In your app initialization (e.g., in initState of your main widget)
final popupService = DynamicPopupService(
repository: MyPopupRepository(),
);
popupService.init(); // Initialize the service
Show Manual Popup #
// Show specific popup by ID
await popupService.showPopupById('privacy_update_2024', context: context);
// Check for and show popup for a screen
await popupService.checkAndShowPopup(
screenName: 'home_screen',
context: context,
);
// Reset state for testing
await popupService.resetPopupState('privacy_update_2024');
// Reset all states
popupService.resetAllPopupStates();
Using Custom Slots #
// Add custom title, footer, and action buttons
final config = PopupConfig(
id: 'custom_popup',
title: 'Custom Popup',
markdownContent: '''
## Welcome!
:::dc<textfield id="name" label="Your Name" placeholder="Enter your name" />dc:::
''',
isBlocking: false,
);
showDialog(
context: context,
builder: (BuildContext context) {
return DynamicPopupWidget(
config: config,
customTitle: const Text('Custom Title', style: TextStyle(color: Colors.white, fontSize: 24)),
customFooter: const Padding(
padding: EdgeInsets.all(16),
child: Text('This is a custom footer', style: TextStyle(fontStyle: FontStyle.italic)),
),
customActions: [
TextButton(
onPressed: () {
// Custom action
print('Custom button pressed');
},
child: const Text('Custom Action'),
),
],
onCompleted: (response) => print('Response: ${response.responses}'),
);
},
);
Manual Popup Display #
``dart // Test specific component final config = PopupConfig( id: 'test_popup', title: 'Test', markdownContent: ''' :::dc
dc::: ''', isBlocking: false, );showDialog( context: context, builder: (BuildContext context) { return DynamicPopupWidget( config: config, onCompleted: (response) => print('Response: ${response.responses}'), ); }, );
## ๐ Custom API Integration
The package is designed to work with any backend API with minimal requirements. Here's how to integrate with your custom API:
### 1. Minimal Implementation (Only 2 Required Methods)
```dart
import 'package:dynamic_popup/dynamic_popup.dart';
class MySimplePopupRepository extends BaseDynamicPopupRepository {
@override
Future<PopupApiResponse?> checkForPopup({
required String screenName,
String? userId,
}) async {
// Only implement these two required methods
// All other methods are optional
}
@override
Future<bool> submitPopupResponse({
required PopupResponse popupResponse,
}) async {
// Only implement these two required methods
// All other methods are optional
}
}
2. Direct Usage Without Service #
// Fetch popup config from your API directly
final popupConfig = await MyApiService.getPopupForScreen('home_screen');
if (popupConfig != null) {
await showDialog(
context: context,
builder: (BuildContext context) {
return DynamicPopupWidget(
config: popupConfig,
onCompleted: (response) async {
// Submit response to your API directly
await MyApiService.submitResponse(response);
Navigator.of(context).pop();
},
);
},
);
}
๐งช Testing #
Test Page #
The example app now includes a comprehensive test page with all popup types and a documentation screen.
๐ Complete Examples #
Privacy Policy Popup #
# Privacy Policy Update
Our privacy policy has been updated. Please review and confirm your preferences.
## Required Consent
:::dc<radiobutton id="privacy_accept" required label="Do you accept the new privacy policy?">
<option id="accept">Accept</option>
<option id="decline">Decline</option>
</radiobutton>dc:::
## Data Usage (Optional)
:::dc<checkbox id="data_usage" label="What data can we use?">
<option id="analytics">Analytics</option>
<option id="marketing">Marketing</option>
<option id="performance">Performance</option>
<option id="crash">Crash Reports</option>
</checkbox>dc:::
## Feedback
:::dc<textarea id="feedback" label="Comments or questions?" placeholder="Share your thoughts..." />dc:::
**Thank you for your attention.**
Complete Survey #
# Satisfaction Survey
Help us improve the app by completing this brief survey.
:::dc<textfield id="name" required label="Full Name" placeholder="Enter your name" />dc:::
:::dc<dropdown id="frequency" required label="Usage Frequency">
<option id="daily">Daily</option>
<option id="weekly">Weekly</option>
<option id="monthly">Monthly</option>
<option id="occasional">Occasional</option>
</dropdown>dc:::
:::dc<checkbox id="features" required label="Which features do you use?">
<option id="restaurant">Restaurant</option>
<option id="sales">Special Sales</option>
<option id="pcm">PCM</option>
<option id="snack">Snack Bar</option>
</checkbox>dc:::
:::dc<textarea id="suggestions" required label="Improvement suggestions" placeholder="Describe your ideas..." />dc:::
:::dc<radiobutton id="recommend" required label="Would you recommend the app?">
<option id="definitely">Definitely</option>
<option id="probably">Probably</option>
<option id="not_sure">Not Sure</option>
<option id="probably_not">Probably Not</option>
<option id="definitely_not">Definitely Not</option>
</radiobutton>dc:::
๐ Architecture #
File Structure #
lib/
โโโ src/
โ โโโ data/
โ โ โโโ model/
โ โ โ โโโ popup_config.dart
โ โ โ โโโ dynamic_component.dart
โ โ โ โโโ popup_models.dart
โ โ โโโ repository/
โ โ โโโ dynamic_popup_repository.dart
โ โ โโโ base_dynamic_popup_repository.dart
โ โโโ parser/
โ โ โโโ markdown_dynamic_parser.dart
โ โโโ service/
โ โ โโโ dynamic_popup_service.dart
โ โโโ ui/
โ โ โโโ components/
โ โ โ โโโ dynamic_radio_button.dart
โ โ โ โโโ dynamic_checkbox.dart
โ โ โ โโโ dynamic_text_area.dart
โ โ โ โโโ dynamic_text_field.dart
โ โ โ โโโ dynamic_dropdown.dart
โ โ โ โโโ dynamic_component_factory.dart
โ โ โโโ dynamic_popup_widget.dart
โโโ dynamic_popup.dart
๐ Security and Privacy #
- All data is validated client-side
- Responses are sent in secure JSON format
- Local storage is encrypted for popup states
- No sensitive data stored permanently
๐จ Customization #
Themes and Styles #
The system uses the current app theme. To customize:
// In dynamic_popup_widget.dart, modify colors:
primaryColor: Theme.of(context).primaryColor,
errorColor: Colors.red.shade600,
backgroundColor: Colors.grey.shade50,
Custom Components #
Add new components by extending DynamicComponentFactory:
case DynamicComponentType.newType:
return NewCustomWidget(/* ... */);
๐จ Troubleshooting #
Popup doesn't appear #
- Verify service is initialized
- Check API logs for errors
- Verify target screen is correct
Parsing errors #
- Check placeholder markdown syntax
- Verify all components have unique IDs
- Ensure options don't contain special characters
Validation issues #
- Check that required fields have values
- Verify data types are correct
- Test with simple popups first
๐ Changelog #
[1.0.4] - 2025-10-09 #
Added #
- Support for conditional required status in addition to conditional visibility
- New
required-when-visibleattribute for components to control required status based on visibility - Case-insensitive conditional logic evaluation for better user experience
- Comprehensive documentation for conditional logic features
- New examples demonstrating both conditional visibility and conditional required status
[1.0.3] - 2025-09-29 #
Added #
- Support for option IDs in radio buttons, checkboxes, and dropdowns
- Smart scrolling for long content in popups
- Support for multiline syntax with newlines and spaces in component definitions
- Custom slots support for titles, footers, and action buttons
- Dedicated examples/documentation screen in the example app
- View Markdown button in example popups
Changed #
- Improved markdown syntax from positional parameters to HTML-like syntax
- Updated component initiator from
:::to:::dc<component>dc:::for better identification - Removed support for old positional parameter syntax
- Updated all examples to use new HTML-like syntax with option IDs
- Enhanced UI components to send option IDs instead of text values in responses
- Consolidated test interface into main example screen
- Improved dialog dismissal handling to prevent overlay issues
Fixed #
- Ensured option IDs are properly used in popup responses instead of text values
- Fixed overlay persistence issue when dismissing non-required popups
1.0.2 #
- Removed http dependency from core library (moved to example project only)
- Removed unused DefaultDynamicPopupRepository from core library
- Added .pubignore file to prevent publishing unnecessary files
- Added LICENSE file with MIT license
- Added CONTRIBUTING.md with contribution guidelines
- Added CODE_OF_CONDUCT.md with community standards
- Fixed flutter analyze issues:
- Removed unused local variable in dynamic_popup_service.dart
- Removed unnecessary imports in dynamic_popup_widget.dart
- Updated deprecated onPopInvoked to onPopInvokedWithResult
- Updated deprecated value parameter to initialValue in dropdown
- Updated documentation to clarify dependency requirements
- Improved package structure for pub.dev publishing
1.0.1 #
- Simplified repository pattern by reducing required methods from 7 to 3
- Removed complex optional methods (markPopupAsShown, markPopupAsDismissed) for easier implementation
- Streamlined example code by removing redundant buttons and simplifying demonstrations
- Improved documentation with clearer, more concise instructions
- Added JSON logging in example apps for better debugging of popup responses
- Enhanced snackbar colors in examples for better readability and user experience
- Fixed undefined variable error in API integration example
- Removed unnecessary http dependency from core library (now only in example)
- Removed unused DefaultDynamicPopupRepository from core library
- Maintained all core functionality while reducing complexity
1.0.0 #
- โ Base dynamic popup system
- โ Markdown parser with placeholders
- โ Interactive UI components
- โ API repository and service
- โ Controller integration
- โ Testing system
- โ Removed GetX dependency for better compatibility
- โ Simplified API requirements (only 2 endpoints required)
๐ Support #
For questions or issues with the dynamic popup system, please open an issue on GitHub.
๐ Conditional Logic #
Dynamic Popup supports conditional logic to show/hide fields or change their required status based on other field values.
Conditional Visibility #
Fields can be shown or hidden based on the value of another field:
:::dc<radiobutton id="consent" required label="Do you consent?">
<option id="yes">Yes</option>
<option id="no">No</option>
</radiobutton>dc:::
:::dc<textfield id="reason" label="Reason for consent" depends-on="consent" when-value="yes" />dc:::
In this example, the "reason" text field will only be visible when the "consent" radio button has the value "yes".
Conditional Required Status #
Fields can change their required status based on the value of another field:
Required When Visible
:::dc<radiobutton id="employment" required label="Are you employed?">
<option id="yes">Yes</option>
<option id="no">No</option>
</radiobutton>dc:::
:::dc<textfield id="employer" label="Employer Name" required-when-visible="true" depends-on="employment" when-value="yes" />dc:::
In this example, the "employer" text field is always visible but becomes required only when the "employment" radio button has the value "yes".
Required When Value
:::dc<radiobutton id="info_needed" required label="Do you have additional information?">
<option id="yes">Yes</option>
<option id="no">No</option>
</radiobutton>dc:::
:::dc<textfield id="additional_info" label="Additional Information" depends-on="info_needed" required-when-value="yes" />dc:::
In this example, the "additional_info" text field is always visible but becomes required only when the "info_needed" radio button has the value "yes", regardless of its visibility.
Available Conditional Attributes #
depends-on: The ID of the component this field depends onwhen-value: The value that triggers the visibility conditionrequired-when-visible: Whether the field should be required when visible (true/false)required-when-value: The value of the depends-on field that makes this field required (instead of a boolean flag)condition: The type of condition to check (default:equals)disable-when-hidden: Whether to disable validation when hidden (default:true)
Supported Conditions #
equals: Field value equals the specified value (default)notEquals: Field value does not equal the specified valuecontains: Field value contains the specified value (for list values like checkboxes)notContains: Field value does not contain the specified value (for list values like checkboxes)
Priority Order #
When multiple conditional required attributes are specified, they are evaluated in this order:
required-when-value(highest priority)required-when-visible(medium priority)- Component's default
requiredattribute (lowest priority)