Integration

Flutter Integration

Add Reqflow to your Flutter app using the webview_flutter package.

Prerequisites

  • Flutter 3.0+Required for latest features
  • Reqflow appCreated 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.1

Then run:

Terminal
flutter pub get

Platform 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.3

Then 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 IndicatorShow a CircularProgressIndicator while the WebView initializes
  • Error HandlingHandle network errors gracefully with user feedback
  • Dark ModeReqflow automatically adapts to the system theme
  • NavigationUse Navigator.push for a natural back button experience