Creating Interactive Flutter UI
Interactive user interfaces allow users to communicate with a Flutter application through taps, swipes, typing, dragging, selecting, scrolling, gestures, dialogs, animations, and other actions. Flutter provides widgets and state-management mechanisms that make it possible to build responsive and engaging applications.
Flutter UI is built from widgets. When the application's state changes because of user interaction, Flutter can rebuild the relevant part of the widget tree to display the updated interface.
1. What is an Interactive UI?
An interactive UI is a user interface that responds to actions performed by the user. Instead of simply displaying information, an interactive application reacts to input and changes its appearance or behavior.
Common User Interactions
- Button taps
- Text input
- Checkbox and switch selection
- Radio button selection
- Dropdown selection
- Slider movement
- Scrolling
- Long press
- Swipe and drag gestures
- Pinch and zoom
- Navigation between screens
- Showing dialogs, menus, and bottom sheets
- Animations based on user actions
2. StatelessWidget vs StatefulWidget
Interactive UI frequently requires state. A StatelessWidget is suitable when its appearance does not change based on internal interaction. A StatefulWidget is used when a widget needs mutable state that can change during its lifetime.
| Feature |
StatelessWidget |
StatefulWidget |
| State |
No mutable internal state |
Can maintain mutable state |
| UI changes |
Usually based on external configuration |
Can change in response to interaction |
| Example |
Text, Icon |
Checkbox, Slider, TextField |
| setState() |
Not available |
Used to trigger rebuilds |
3. Understanding State in Interactive UI
State is data that can change while the application is running and affects what the user sees. For example, a favorite button may have two states: favorite and not favorite.
When the state changes, Flutter can rebuild the widget so that the UI represents the new state.
bool isFavorite = false;
After a user taps the favorite button, the value can change:
setState(() {
isFavorite = !isFavorite;
});
4. Using setState() for Interactive UI
setState() is commonly used for simple local widget state. It tells Flutter that the state has changed and that the widget should rebuild.
Example: Interactive Favorite Button
import 'package:flutter/material.dart';
class FavoriteButton extends StatefulWidget {
const FavoriteButton({super.key});
@override
State createState() => _FavoriteButtonState();
}
class _FavoriteButtonState extends State {
bool isFavorite = false;
@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(
isFavorite ? Icons.favorite : Icons.favorite_border,
color: isFavorite ? Colors.red : Colors.grey,
),
onPressed: () {
setState(() {
isFavorite = !isFavorite;
});
},
);
}
}
How It Works
- The initial value of
isFavorite is false.
- The outline heart is displayed.
- The user taps the button.
setState() changes the value.
- Flutter rebuilds the widget.
- The filled heart is displayed.
5. Buttons in Interactive Flutter UI
Flutter provides several buttons for common interactions.
ElevatedButton
TextButton
OutlinedButton
IconButton
FloatingActionButton
ElevatedButton Example
ElevatedButton(
onPressed: () {
print('Button pressed');
},
child: const Text('Click Me'),
)
TextButton Example
TextButton(
onPressed: () {
print('Text button pressed');
},
child: const Text('Learn More'),
)
IconButton Example
IconButton(
icon: const Icon(Icons.settings),
onPressed: () {
print('Settings opened');
},
)
FloatingActionButton Example
FloatingActionButton(
onPressed: () {
print('Add action');
},
child: const Icon(Icons.add),
)
6. Handling User Input with TextField
TextField allows users to enter text. It is commonly used for login forms, search fields, registration forms, comments, and other input areas.
TextField(
decoration: const InputDecoration(
labelText: 'Enter your name',
border: OutlineInputBorder(),
),
)
Using TextEditingController
final TextEditingController nameController = TextEditingController();
TextField(
controller: nameController,
decoration: const InputDecoration(
labelText: 'Name',
),
)
The entered value can be accessed using:
String name = nameController.text;
When a controller is created inside a stateful widget, remember to dispose of it:
@override
void dispose() {
nameController.dispose();
super.dispose();
}
7. Handling Checkbox Interaction
A Checkbox allows users to select or clear a boolean option.
bool accepted = false;
Checkbox(
value: accepted,
onChanged: (value) {
setState(() {
accepted = value ?? false;
});
},
)
Practical Example
Row(
children: [
Checkbox(
value: accepted,
onChanged: (value) {
setState(() {
accepted = value ?? false;
});
},
),
const Expanded(
child: Text('I accept the terms and conditions'),
),
],
)
8. Using Switch for Interactive Settings
A Switch is useful when users need to enable or disable a setting.
bool notificationsEnabled = true;
Switch(
value: notificationsEnabled,
onChanged: (value) {
setState(() {
notificationsEnabled = value;
});
},
)
Common Use Cases
- Dark mode
- Notifications
- Location services
- Auto-save
- Sound settings
- Privacy settings
9. Radio Buttons
Radio buttons are useful when the user must choose one option from a group.
String selectedPlan = 'Basic';
Column(
children: [
RadioListTile(
title: const Text('Basic'),
value: 'Basic',
groupValue: selectedPlan,
onChanged: (value) {
setState(() {
selectedPlan = value!;
});
},
),
RadioListTile(
title: const Text('Premium'),
value: 'Premium',
groupValue: selectedPlan,
onChanged: (value) {
setState(() {
selectedPlan = value!;
});
},
),
],
)
10. Dropdown Selection
A dropdown allows users to select one value from a list of available choices.
String selectedCity = 'Mumbai';
DropdownButton(
value: selectedCity,
items: const [
DropdownMenuItem(
value: 'Mumbai',
child: Text('Mumbai'),
),
DropdownMenuItem(
value: 'Delhi',
child: Text('Delhi'),
),
DropdownMenuItem(
value: 'Pune',
child: Text('Pune'),
),
],
onChanged: (value) {
setState(() {
selectedCity = value!;
});
},
)
11. Slider Interaction
A Slider lets users select a numeric value by moving a thumb along a track.
double volume = 50;
Slider(
value: volume,
min: 0,
max: 100,
divisions: 10,
label: volume.round().toString(),
onChanged: (value) {
setState(() {
volume = value;
});
},
)
Common Slider Applications
- Volume control
- Brightness
- Price range
- Progress selection
- Rating or percentage selection
12. GestureDetector
GestureDetector detects gestures performed by the user. It can recognize taps, long presses, drags, and other gestures.
GestureDetector(
onTap: () {
print('Container tapped');
},
child: Container(
width: 200,
height: 100,
color: Colors.blue,
child: const Center(
child: Text(
'Tap Me',
style: TextStyle(color: Colors.white),
),
),
),
)
Common GestureDetector Callbacks
| Callback |
Purpose |
| onTap |
Detects a normal tap |
| onDoubleTap |
Detects a double tap |
| onLongPress |
Detects a long press |
| onPanUpdate |
Detects movement in any direction |
| onHorizontalDragUpdate |
Detects horizontal dragging |
| onVerticalDragUpdate |
Detects vertical dragging |
| onScaleUpdate |
Detects scaling gestures |
13. InkWell for Material Tap Effects
InkWell provides a Material-style visual response when a user interacts with a widget, such as a ripple effect.
InkWell(
onTap: () {
print('Card tapped');
},
child: Container(
padding: const EdgeInsets.all(20),
child: const Text('Interactive Card'),
),
)
InkWell is useful when you want both interaction and visual feedback.
14. Interactive Cards
Cards are frequently used in dashboards, shopping applications, profile screens, and content-based applications.
Card(
child: InkWell(
onTap: () {
print('Product selected');
},
child: const Padding(
padding: EdgeInsets.all(20),
child: Row(
children: [
Icon(Icons.shopping_bag),
SizedBox(width: 12),
Text('Product Details'),
],
),
),
),
)
15. Interactive Lists
List items can respond to taps using ListTile, InkWell, or GestureDetector.
ListTile(
leading: const Icon(Icons.person),
title: const Text('Profile'),
subtitle: const Text('View your profile'),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: () {
print('Profile selected');
},
)
16. Dismissible Widgets
Dismissible allows users to remove or dismiss an item by swiping it.
Dismissible(
key: const ValueKey('item-1'),
onDismissed: (direction) {
print('Item dismissed');
},
background: Container(
color: Colors.red,
child: const Icon(Icons.delete),
),
child: const ListTile(
title: Text('Swipe to delete'),
),
)
Common Use Cases
- Delete an email
- Remove a shopping-cart item
- Dismiss a notification
- Remove a task from a list
17. InteractiveViewer
InteractiveViewer can provide pan and zoom interactions for content.
InteractiveViewer(
minScale: 0.5,
maxScale: 4.0,
child: Image.asset('assets/map.png'),
)
This is useful for maps, diagrams, large images, floor plans, and documents.
18. Showing Dialogs
Dialogs allow an application to temporarily request attention or input from the user.
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Delete Item'),
content: const Text(
'Are you sure you want to delete this item?',
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Delete'),
),
],
);
},
);
19. SnackBar Feedback
A SnackBar provides short feedback after an action.
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Item added successfully'),
),
);
SnackBars are useful for confirmations, errors, and temporary status messages.
20. Bottom Sheets
A bottom sheet displays additional actions or information from the bottom of the screen.
showModalBottomSheet(
context: context,
builder: (context) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.camera_alt),
title: const Text('Camera'),
onTap: () {
Navigator.pop(context);
},
),
ListTile(
leading: const Icon(Icons.photo),
title: const Text('Gallery'),
onTap: () {
Navigator.pop(context);
},
),
],
),
);
},
);
21. Interactive Forms
Flutter provides Form and FormField widgets for collecting and validating user input.
final formKey = GlobalKey();
Form(
key: formKey,
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(
labelText: 'Email',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
return null;
},
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
if (formKey.currentState!.validate()) {
print('Form submitted');
}
},
child: const Text('Submit'),
),
],
),
)
22. Navigation as an Interaction
Navigation allows users to move between screens. Flutter's Navigator maintains a stack of routes and provides methods such as push() and pop().
Navigate to Another Screen
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const DetailsScreen(),
),
);
},
child: const Text('Open Details'),
)
Return to the Previous Screen
Navigator.pop(context);
23. Interactive Navigation Bar
Navigation components can change the visible content when users select different destinations.
int selectedIndex = 0;
NavigationBar(
selectedIndex: selectedIndex,
onDestinationSelected: (index) {
setState(() {
selectedIndex = index;
});
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.person_outline),
selectedIcon: Icon(Icons.person),
label: 'Profile',
),
],
)
24. Interactive Tabs
TabBar and TabBarView can be used to switch between related sections.
DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
bottom: const TabBar(
tabs: [
Tab(text: 'Products'),
Tab(text: 'Reviews'),
],
),
),
body: const TabBarView(
children: [
Center(child: Text('Products')),
Center(child: Text('Reviews')),
],
),
),
)
25. Animated Interactive UI
Animations can make interactions easier to understand and provide visual feedback. Flutter includes implicit animation widgets such as AnimatedContainer, AnimatedOpacity, AnimatedScale, and AnimatedPositioned, along with explicit animation widgets such as FadeTransition, SlideTransition, ScaleTransition, and RotationTransition.
AnimatedContainer Example
bool expanded = false;
AnimatedContainer(
duration: const Duration(milliseconds: 400),
width: expanded ? 300 : 150,
height: expanded ? 200 : 100,
color: expanded ? Colors.blue : Colors.grey,
child: Center(
child: ElevatedButton(
onPressed: () {
setState(() {
expanded = !expanded;
});
},
child: const Text('Animate'),
),
),
)
26. Interactive Opacity
AnimatedOpacity can smoothly show or hide content based on state.
bool visible = true;
AnimatedOpacity(
opacity: visible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 300),
child: const Text('Interactive Content'),
)
27. Interactive Scale
An interactive scale effect can make a button or card appear to respond when selected.
AnimatedScale(
scale: selected ? 1.1 : 1.0,
duration: const Duration(milliseconds: 200),
child: const Icon(Icons.favorite, size: 50),
)
28. Combining Interaction and State
A common pattern is to capture an event, update state, and rebuild the UI.
void _toggle() {
setState(() {
isActive = !isActive;
});
}
The general flow is:
- User performs an action.
- Flutter detects the interaction.
- A callback is executed.
- The application updates its state.
- Flutter rebuilds the relevant UI.
- The user sees the new result.
29. Interactive UI with Conditional Rendering
Conditional expressions can display different widgets depending on the current state.
Column(
children: [
Text(isLoggedIn ? 'Welcome Back' : 'Please Login'),
if (isLoggedIn)
ElevatedButton(
onPressed: logout,
child: const Text('Logout'),
)
else
ElevatedButton(
onPressed: login,
child: const Text('Login'),
),
],
)
30. Loading, Success, Error, and Empty States
Interactive applications frequently communicate the status of asynchronous operations.
if (isLoading) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (hasError) {
return const Center(
child: Text('Something went wrong'),
);
}
if (items.isEmpty) {
return const Center(
child: Text('No items available'),
);
}
return ListView(
children: items.map((item) {
return ListTile(
title: Text(item),
);
}).toList(),
);
31. Building a Complete Interactive Counter
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const CounterScreen(),
);
}
}
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State createState() => _CounterScreenState();
}
class _CounterScreenState extends State {
int count = 0;
void increment() {
setState(() {
count++;
});
}
void decrement() {
setState(() {
if (count > 0) {
count--;
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Interactive Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'$count',
style: const TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: decrement,
icon: const Icon(Icons.remove),
),
IconButton(
onPressed: increment,
icon: const Icon(Icons.add),
),
],
),
],
),
),
);
}
}
32. Building an Interactive Product Card
class ProductCard extends StatefulWidget {
const ProductCard({super.key});
@override
State createState() => _ProductCardState();
}
class _ProductCardState extends State {
bool isFavorite = false;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
const Icon(
Icons.shopping_bag,
size: 80,
),
const Text(
'Flutter Course',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const Text('Learn Flutter development'),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Course added to cart'),
),
);
},
child: const Text('Add to Cart'),
),
IconButton(
onPressed: () {
setState(() {
isFavorite = !isFavorite;
});
},
icon: Icon(
isFavorite
? Icons.favorite
: Icons.favorite_border,
),
),
],
),
],
),
),
);
}
}
33. Interactive UI Architecture
For small widgets, local state with setState() can be sufficient. As applications become larger, state may need to be shared between multiple widgets or managed using solutions such as Provider, Riverpod, Bloc, or other state-management approaches.
Typical Interaction Architecture
User Action
↓
Gesture / Button / Input
↓
Callback
↓
State Update
↓
Business Logic
↓
UI Rebuild
↓
Updated Screen
34. Local State vs Shared State
| State Type |
Example |
Typical Approach |
| Local UI state |
Selected tab |
setState() |
| Form state |
Text fields and validation |
Form, controllers, local state |
| Shared application state |
Shopping cart |
Provider, Riverpod, Bloc, etc. |
| Remote state |
API/Firebase data |
Repository and state-management solution |
35. Interactive UI Best Practices
- Use the simplest suitable interaction widget.
- Provide clear visual feedback after user actions.
- Use
setState() for small local state.
- Keep business logic separate from complex UI code.
- Use meaningful labels for interactive controls.
- Do not make the entire screen interactive when only a small area needs interaction.
- Validate user input before processing it.
- Show loading indicators for longer operations.
- Display useful error and empty states.
- Dispose controllers when they are no longer needed.
- Avoid unnecessary widget rebuilds.
- Use animations purposefully rather than adding animation everywhere.
- Consider touch targets and accessibility when designing interactive controls.
36. Common Mistakes
- Changing state without calling
setState() when local widget state needs rebuilding.
- Calling
setState() after a widget has been disposed.
- Forgetting to dispose
TextEditingController or other controllers.
- Putting large amounts of business logic directly inside
build().
- Creating unnecessarily complex gesture handling.
- Providing no feedback after an important user action.
- Ignoring loading, error, and empty states.
- Using too many animations that distract from the main task.
- Making interactive elements difficult to discover.
37. Practical Interactive UI Project
A useful practice project is an interactive shopping application.
Suggested Features
- Product list
- Product detail screen
- Favorite button
- Add-to-cart button
- Quantity increment and decrement
- Search field
- Category selection
- Price slider
- Product filtering
- Swipe-to-delete cart items
- Navigation between screens
- Animated product selection
- Checkout form
- Success and error feedback
Example Interaction Flow
Product List
↓
Tap Product
↓
Product Details
↓
Select Quantity
↓
Add to Cart
↓
Show SnackBar
↓
Open Cart
↓
Checkout Form
↓
Validate Form
↓
Show Confirmation
38. Interactive Flutter UI Checklist
| Requirement |
Flutter Feature |
| Button interaction |
ElevatedButton, TextButton, IconButton |
| Custom tap interaction |
GestureDetector |
| Ripple feedback |
InkWell |
| Text input |
TextField, TextFormField |
| Boolean selection |
Checkbox, Switch |
| Single selection |
Radio, Dropdown |
| Numeric selection |
Slider |
| Swipe interaction |
Dismissible |
| Pan and zoom |
InteractiveViewer |
| Temporary feedback |
SnackBar |
| Confirmation |
AlertDialog |
| Additional actions |
BottomSheet |
| Screen navigation |
Navigator |
| Animated response |
AnimatedContainer, AnimatedOpacity, transitions |
39. Interview Questions
Q1. What is an interactive UI in Flutter?
An interactive UI responds to user actions such as taps, typing, gestures, selections, scrolling, and navigation.
Q2. What is the difference between StatelessWidget and StatefulWidget?
A StatelessWidget does not maintain mutable internal state, while a StatefulWidget has a separate State object that can store changing values and trigger UI updates.
Q3. What does setState() do?
setState() informs Flutter that local state has changed and that the widget should rebuild to reflect the updated state.
Q4. What is GestureDetector?
GestureDetector detects gestures such as taps, long presses, drags, and scaling gestures.
Q5. What is InkWell?
InkWell provides Material-style interaction feedback such as a ripple effect and supports tap-related callbacks.
Q6. How can a Flutter application validate user input?
Flutter provides Form and FormField-based widgets such as TextFormField, which can use validator functions.
Q7. How can animations improve interactive UI?
Animations can provide visual feedback, communicate changes, guide attention, and make transitions between UI states easier to understand.
40. Quick Revision
- Interactive UI responds to user actions.
- State determines how dynamic widgets appear.
setState() is useful for simple local state.
- Buttons provide common interaction patterns.
TextField collects user input.
Checkbox, Switch, Radio, and Dropdown support selection.
Slider supports numeric selection.
GestureDetector handles custom gestures.
InkWell provides Material interaction feedback.
Dismissible supports swipe-based actions.
InteractiveViewer supports pan and zoom.
Dialog, SnackBar, and BottomSheet communicate with users.
Navigator supports screen-to-screen interaction.
- Animations can make state changes and navigation more understandable.
- For larger applications, use an appropriate state-management architecture.
41. Learning Outcome
After completing this topic, you should be able to create Flutter interfaces that respond to taps, text input, selections, gestures, scrolling, navigation, dialogs, and other user actions. You should also understand how state changes drive UI updates and how interactive widgets can be combined to create practical application screens.
42. JustAcademy Flutter Resources
Learn more about Flutter development through the JustAcademy Flutter Training Course.
You can also Register for Flutter Course Demo to explore the training program.
43. Summary
Creating an interactive Flutter UI involves combining widgets, state, callbacks, gestures, forms, navigation, feedback components, and animations. Flutter provides ready-made interactive widgets as well as flexible tools such as GestureDetector for custom interactions. By managing state correctly and providing clear feedback, developers can build responsive, user-friendly, and engaging Flutter applications.