Integration
Flutter Integration
Add Reqflow to your Flutter app using the webview_flutter package.
Prerequisites
- Flutter 3.0+ — Required for latest features
- Reqflow app — Created in the dashboard
Install Dependencies
Add the required packages to your pubspec.yaml:
pubspec.yaml
dependencies:
webview_flutter: ^4.4.2
device_info_plus: ^9.1.1Then run:
Terminal
flutter pub getPlatform Configuration
Android
Set the minimum SDK version in android/app/build.gradle:
android/app/build.gradle
android {
defaultConfig {
minSdkVersion 20
// ...
}
}iOS
Add the following to ios/Runner/Info.plist:
ios/Runner/Info.plist
<key>io.flutter.embedded_views_preview</key>
<true/>Basic Implementation
Create a FeedbackScreen widget:
feedback_screen.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'dart:io';
class FeedbackScreen extends StatefulWidget {
final String appId;
final String? userId;
final String userName;
final String? userEmail;
const FeedbackScreen({
super.key,
required this.appId,
this.userId,
required this.userName,
this.userEmail,
});
@override
State<FeedbackScreen> createState() => _FeedbackScreenState();
}
class _FeedbackScreenState extends State<FeedbackScreen> {
late WebViewController _controller;
bool _isLoading = true;
@override
void initState() {
super.initState();
_initWebView();
}
Future<void> _initWebView() async {
final deviceId = await _getDeviceId();
final userId = widget.userId ?? deviceId;
final params = {
'user_id': userId,
'user_name': widget.userName,
if (widget.userEmail != null) 'user_email': widget.userEmail!,
};
final uri = Uri.parse('https://reqflow.com/vote/${widget.appId}')
.replace(queryParameters: params);
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageFinished: (_) {
setState(() => _isLoading = false);
},
onWebResourceError: (error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to load: ${error.description}')),
);
},
),
)
..loadRequest(uri);
setState(() {});
}
Future<String> _getDeviceId() async {
final deviceInfo = DeviceInfoPlugin();
if (Platform.isAndroid) {
final androidInfo = await deviceInfo.androidInfo;
return androidInfo.id;
} else if (Platform.isIOS) {
final iosInfo = await deviceInfo.iosInfo;
return iosInfo.identifierForVendor ?? 'unknown';
}
return 'unknown';
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Feedback'),
),
body: Stack(
children: [
WebViewWidget(controller: _controller),
if (_isLoading)
const Center(
child: CircularProgressIndicator(),
),
],
),
);
}
}Usage Example
Navigate to the feedback screen from anywhere in your app:
settings_screen.dart
// In your settings or main screen
class SettingsScreen extends StatelessWidget {
const SettingsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Settings')),
body: ListView(
children: [
ListTile(
leading: const Icon(Icons.feedback),
title: const Text('Send Feedback'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const FeedbackScreen(
appId: 'your-app-id',
userName: 'John Doe',
userEmail: 'john@example.com',
),
),
);
},
),
],
),
);
}
}With Provider/Riverpod
If you're using a state management solution, pass user data from your auth state:
// Using Provider
class FeedbackScreenWrapper extends StatelessWidget {
const FeedbackScreenWrapper({super.key});
@override
Widget build(BuildContext context) {
final user = Provider.of<AuthProvider>(context).currentUser;
return FeedbackScreen(
appId: 'your-app-id',
userId: user?.id,
userName: user?.name ?? 'Anonymous',
userEmail: user?.email,
);
}
}Consistent User ID
Use the device ID or your auth system's user ID. Avoid generating new IDs each session, or users will lose their votes.
Secure Authentication (Optional)
For enhanced security, you can add HMAC signature verification to prevent user impersonation. First, add the crypto package to your pubspec.yaml:
pubspec.yaml
crypto: ^3.0.3Then use the signature helper:
signature_helper.dart
import 'dart:convert';
import 'package:crypto/crypto.dart';
/// Generate HMAC signature for secure authentication (optional)
class SignatureHelper {
static Map<String, String> generateSignature(String userId, String secret) {
final timestamp = (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString();
final payload = '$userId:$timestamp';
final hmac = Hmac(sha256, utf8.encode(secret));
final signature = hmac.convert(utf8.encode(payload)).toString();
return {
'signature': signature,
'timestamp': timestamp,
};
}
}
// Usage in FeedbackScreen
Future<void> _initWebViewWithSignature() async {
final deviceId = await _getDeviceId();
final userId = widget.userId ?? deviceId;
final secret = await getSecretFromBackend(); // Fetch from your backend
final sig = SignatureHelper.generateSignature(userId, secret);
final params = {
'user_id': userId,
'user_name': widget.userName,
'signature': sig['signature']!,
'timestamp': sig['timestamp']!,
if (widget.userEmail != null) 'user_email': widget.userEmail!,
};
final uri = Uri.parse('https://reqflow.com/vote/${widget.appId}')
.replace(queryParameters: params);
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..loadRequest(uri);
}Security Note
Don't hardcode the secret in your app. Fetch it from your backend or use Flutter Secure Storage. The signature feature is optional and only needed if you enable it on your Reqflow server.
Best Practices
- Loading Indicator — Show a CircularProgressIndicator while the WebView initializes
- Error Handling — Handle network errors gracefully with user feedback
- Dark Mode — Reqflow automatically adapts to the system theme
- Navigation — Use Navigator.push for a natural back button experience