slot_machine_ui
A highly customizable slot-machine-style UI widget for Flutter, featuring smooth reel animations, a skeuomorphic design, and optional sound effects.

📋 Table of Contents
- Features
- Installation
- Basic Usage
- Advanced Usage
- Customization Options
- Examples
- FAQ
- Contributing
- License
✨ Features
- 🎰 Smooth reel animations with configurable spin duration
- 🎨 Clean skeuomorphic-inspired design with optional inner frame
- 🎵 Advanced sound system with custom win/lose logic
- 🔧 Highly customizable layout, colors, and behavior
- 📱 Responsive sizing for different screen widths
- 🎮 Controller-based API for programmatic control
- 🌐 Cross-platform support (iOS, Android, Web, Desktop)
📦 Installation
Add the dependency to your pubspec.yaml:
dependencies:
slot_machine_ui: ^1.3.6
Then run:
flutter pub get
Or install directly:
flutter pub add slot_machine_ui
🚀 Basic Usage
import 'package:flutter/material.dart';
import 'package:slot_machine_ui/slot_machine_ui.dart';
class MySlotMachine extends StatefulWidget {
@override
State<MySlotMachine> createState() => _MySlotMachineState();
}
class _MySlotMachineState extends State<MySlotMachine> {
final _controller = SlotMachineController();
final _symbols = ['🍒', '🍋', '7️⃣', '💎', '⭐', '🔔'];
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SlotMachineWidget(
primaryColor: Colors.redAccent,
showInnerFrame: true,
width: 300,
controller: _controller,
symbols: _symbols,
onSpinStart: () {
debugPrint('Spin started');
},
onResult: (results) {
debugPrint('Spin result: $results');
},
onSpinEnd: () {
debugPrint('Spin ended');
},
),
),
);
}
}
🎨 Advanced Usage
Custom Colors and Layout
SlotMachineWidget(
controller: _controller,
symbols: _symbols,
numberOfReels: 4,
visibleSymbolsPerReel: 3,
width: 400,
primaryColor: Color(0xFF2C3E50),
accentColor: Color(0xFFE74C3C),
reelBackgroundColor: Colors.white,
buttonPosition: SpinButtonPosition.below,
buttonText: 'PULL',
showInnerFrame: true, // Add decorative inner frame
)
Customizing Animation Speed
Control the spin animation duration with the spinDuration parameter (in milliseconds):
SlotMachineWidget(
controller: _controller,
symbols: _symbols,
spinDuration: 5000, // 5 seconds - slow, dramatic spin
)
// Fast spin (1.5 seconds)
SlotMachineWidget(
controller: _controller,
symbols: _symbols,
spinDuration: 1500,
)
// Default speed (3 seconds)
SlotMachineWidget(
controller: _controller,
symbols: _symbols,
// spinDuration: 3000 (default, can be omitted)
)
How it works: The spinDuration controls the entire animation sequence:
- 60% of the time: all reels spin together
- 30% of the time: reels stop one by one (staggered)
- 10% of the time: final pause before showing results
The spin sound plays throughout the entire duration until the result sound plays.
Using Image Assets
First, add your images to pubspec.yaml:
flutter:
assets:
- assets/images/cherry.png
- assets/images/lemon.png
- assets/images/seven.png
Then use them in your widget:
SlotMachineWidget(
controller: _controller,
symbols: [
'assets/images/cherry.png',
'assets/images/lemon.png',
'assets/images/seven.png',
],
useImageAssets: true, // ⚠️ Don't forget this!
)
Advanced Sound System with Custom Win Logic
The latest version (1.3.6) includes a powerful sound system that lets you define custom win conditions and play different sounds based on the results.
Basic Sound Setup
- Add sound files to your project's
pubspec.yaml:
flutter:
assets:
- assets/sounds/spin.mp3
- assets/sounds/win.mp3
- assets/sounds/lose.mp3
- Configure basic sounds:
SlotMachineWidget(
controller: _controller,
symbols: _symbols,
// Both path formats are supported:
spinSoundAsset: 'sounds/spin.mp3', // ✅ Recommended
winSoundAsset: 'assets/sounds/win.mp3', // Plays on default win
loseSoundAsset: 'assets/sounds/lose.mp3', // Plays on default lose
)
Custom Win/Lose Logic with onGetWinSound
The onGetWinSound callback gives you complete control over which sound plays based on the spin results. This is perfect for:
- Multiple win types (jackpot, big win, small win)
- Special symbol combinations
- Custom game logic
How it works:
- Return a sound asset path (String) to indicate a win and play that sound
- Return
nullto play theloseSoundAsset - If you don't provide
onGetWinSound, it uses default logic (all symbols in first row match = win)
Example 1: Different sounds for different wins
SlotMachineWidget(
controller: _controller,
symbols: ['🍒', '🍋', '💎', '7️⃣'],
spinSoundAsset: 'sounds/spin.mp3',
loseSoundAsset: 'sounds/lose.mp3',
onGetWinSound: (results) {
// results is List<List<String>> - one list per reel
// results[0] = first reel, results[0][0] = top symbol of first reel
// Check for jackpot (all 7s in top row)
if (results.every((reel) => reel[0] == '7️⃣')) {
return 'sounds/jackpot.mp3';
}
// Check for diamond win (all diamonds in top row)
if (results.every((reel) => reel[0] == '💎')) {
return 'sounds/big_win.mp3';
}
// Check for any matching top row
if (results.every((reel) => reel[0] == results[0][0])) {
return 'sounds/small_win.mp3';
}
// No win - return null to play lose sound
return null;
},
)
Example 2: Check multiple rows for wins
onGetWinSound: (results) {
// Check all rows for matches
for (int row = 0; row < results[0].length; row++) {
bool rowMatches = results.every((reel) => reel[row] == results[0][row]);
if (rowMatches) {
return 'sounds/win.mp3';
}
}
return null; // No matches found
},
Example 3: Diagonal wins
onGetWinSound: (results) {
// Check diagonal (top-left to bottom-right)
if (results.length == 3 && results[0].length == 3) {
if (results[0][0] == results[1][1] && results[1][1] == results[2][2]) {
return 'sounds/diagonal_win.mp3';
}
}
return null;
},
Example 4: Special symbol combinations
onGetWinSound: (results) {
final topRow = results.map((reel) => reel[0]).toList();
// Check for specific combination: cherry-lemon-cherry
if (topRow.length == 3 &&
topRow[0] == '🍒' &&
topRow[1] == '🍋' &&
topRow[2] == '🍒') {
return 'sounds/special_combo.mp3';
}
// Check if at least 2 symbols match
final symbolCounts = <String, int>{};
for (var symbol in topRow) {
symbolCounts[symbol] = (symbolCounts[symbol] ?? 0) + 1;
}
if (symbolCounts.values.any((count) => count >= 2)) {
return 'sounds/partial_win.mp3';
}
return null;
},
Sound behavior:
- Spin sound plays from the start of the animation until just before the result
- The spin sound automatically stops before the result sound plays
onGetWinSoundis called after all reels stop, before any result sound plays- If
onGetWinSoundreturns a path, that sound plays - If
onGetWinSoundreturnsnull, theloseSoundAssetplays - If
onGetWinSoundis not provided, default win logic is used (top row match =winSoundAsset, elseloseSoundAsset)
Supported audio formats: MP3, WAV, OGG, AAC
Platform notes:
- iOS/Android: Works out of the box
- Web: May require user interaction before playing (browser security)
- Desktop: Fully supported
Technical details: Sound playback uses the audioplayers package, which is included as a dependency. If audio fails to load, the widget gracefully falls back to haptic feedback.
Troubleshooting:
- Ensure asset paths in
pubspec.yamlare properly indented - Run
flutter clean && flutter pub getafter adding new assets - Check that audio files exist at the specified paths
- Test on actual devices (not just emulator) for best results
Programmatic Control
// Spin programmatically
_controller.spin();
// Check if spinning
if (_controller.isSpinning) {
print('Currently spinning!');
}
// Get results
if (_controller.finalResults != null) {
print('Last result: ${_controller.finalResults}');
}
Callbacks
SlotMachineWidget(
controller: _controller,
symbols: _symbols,
onSpinStart: () {
print('Spin started!');
},
onResult: (results) {
// Check for wins
bool hasWin = results.every((reel) => reel[0] == results[0][0]);
if (hasWin) {
print('Winner! 🎉');
}
},
onSpinEnd: () {
print('Spin completed!');
},
)
⚙️ Customization Options
| Parameter | Type | Default | Description |
|---|---|---|---|
controller |
SlotMachineController |
required | Controller to manage state |
symbols |
List<String> |
required | List of symbols (emoji or asset paths) |
useImageAssets |
bool |
false |
Whether symbols are image asset paths |
numberOfReels |
int |
3 |
Number of reels (columns) |
visibleSymbolsPerReel |
int |
3 |
Visible symbols per reel (rows) |
width |
double? |
null |
Width of machine (auto-sized if null) |
spinDuration |
int |
3000 |
Total animation duration in milliseconds |
primaryColor |
Color |
Color(0xFFE0E0E0) |
Main machine body color |
accentColor |
Color |
Color(0xFFFF3333) |
Spin button color |
reelBackgroundColor |
Color |
Color(0xFFFAFAFA) |
Background color of reels |
showSpinButton |
bool |
true |
Whether to show the spin button |
showInnerFrame |
bool |
false |
Whether to show decorative inner frame |
buttonPosition |
SpinButtonPosition |
below |
Position of button (below/above/left/right) |
buttonText |
String |
'SPIN' |
Text on the spin button |
spinSoundAsset |
String? |
null |
Asset path for spin sound |
winSoundAsset |
String? |
null |
Asset path for default win sound |
loseSoundAsset |
String? |
null |
Asset path for lose sound |
onGetWinSound |
String? Function(List<List<String>>)? |
null |
Callback to determine which sound to play based on results |
onResult |
Function? |
null |
Callback with final grid results |
onSpinStart |
Function? |
null |
Callback when spin starts |
onSpinEnd |
Function? |
null |
Callback when spin ends |
Button Positions
enum SpinButtonPosition {
below, // Button below the machine
above, // Button above the machine
right, // Button to the right
left, // Button to the left
}
📱 Examples
Check out the example folder for complete examples:
- Basic emoji slot machine - Simple setup with emoji symbols
- Custom themed machine - Custom colors and image assets
- Sound-enabled machine - With spin and win sound effects
- Advanced sound logic - Custom win detection with multiple sounds
- Programmatic control - Controlling spins from code
- Variable speed - Different animation durations
To run the example app:
cd example
flutter run
💡 Tips
- Performance: Use 3-6 symbols for best performance
- Responsive Design: Leave
widthasnullto auto-size based on screen width - Win Detection: Use
onGetWinSoundcallback for custom win logic and sound selection - Accessibility: Consider adding haptic feedback for better UX
- Asset Optimization: Compress images and use appropriate formats (PNG for symbols, MP3/AAC for sounds)
- Animation Speed:
- Fast spins (1000-2000ms) work well for casual games
- Medium spins (2500-3500ms) provide balanced excitement
- Slow spins (4000-6000ms) create dramatic tension
- Sound Design: Create different sounds for various win types to enhance user experience
❓ FAQ
Q: Can I use this in a production gambling app?
A: This package is for educational and entertainment purposes. Ensure compliance with local gambling laws and regulations.
Q: Why isn't my sound playing?
A: Make sure the asset paths are correctly added to pubspec.yaml and the files exist in your project.
Q: How do I detect a win?
A: Use the onGetWinSound callback for custom win logic:
onGetWinSound: (results) {
// Check if all reels have the same top symbol
bool isWin = results.every((reel) => reel[0] == results[0][0]);
return isWin ? 'sounds/win.mp3' : null;
}
Q: What's the difference between winSoundAsset and onGetWinSound?
A: winSoundAsset is a fallback used when onGetWinSound is not provided or when you're using default win logic. onGetWinSound gives you full control to play different sounds for different types of wins.
Q: Can I customize the animation duration?
A: Yes! Use the spinDuration parameter to control the total animation time in milliseconds. Default is 3000ms (3 seconds).
Q: Why aren't my images showing up?
A: Make sure you've set useImageAssets: true when using image paths in the symbols list.
Q: Does this work on web?
A: Yes! The package is tested on all Flutter platforms (iOS, Android, Web, Desktop).
Q: Can the spin sound play for the entire duration?
A: Yes! The spin sound automatically plays throughout the entire animation (controlled by spinDuration) and stops just before the result sound plays.
Q: How do I check multiple rows for wins?
A: Loop through rows in onGetWinSound:
onGetWinSound: (results) {
for (int row = 0; row < results[0].length; row++) {
bool rowMatches = results.every((reel) => reel[row] == results[0][row]);
if (rowMatches) return 'sounds/win.mp3';
}
return null;
}
🤝 Contributing
Contributions are welcome! Here's how you can help:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please make sure to:
- Follow the existing code style
- Add tests for new features
- Update documentation as needed
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
⚠️ Disclaimer
This package is for educational and entertainment purposes only. Please ensure compliance with local gambling laws and regulations if used in production applications.
🙏 Acknowledgments
- Inspired by classic slot machine designs
- Built with Flutter and ❤️
Made with Flutter 💙