safety_net_flutter 1.2.1
safety_net_flutter: ^1.2.1 copied to clipboard
Flutter plugin wrapping SafetyNet (iOS) and SafetyNetAndroid for report-only root/jailbreak/tamper/debugger detection on both platforms.
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:safety_net_flutter/safety_net_flutter.dart';
// Replace with your own Google Cloud project number (linked to your Play
// Console app) and a real per-action request hash — see
// https://developer.android.com/google/play/integrity/setup.
const int _kCloudProjectNumber = 0;
const String _kRequestHash = 'example-request-hash';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
SafetyNetCheckResult? _result;
String? _error;
String? _integrityStatus;
@override
void initState() {
super.initState();
runCheck();
}
Future<void> runCheck() async {
SafetyNetCheckResult result;
try {
result = await SafetyNetFlutter.check();
} catch (e) {
if (!mounted) return;
setState(() => _error = e.toString());
return;
}
if (!mounted) return;
setState(() {
_result = result;
_error = null;
});
}
// Play Integrity is Android-only — see the README's "Play Integrity API"
// section. Calling these on iOS rejects with a PlatformException rather
// than crashing; this demo just gates the buttons with Platform.isAndroid
// instead, since the outcome is already known.
Future<void> prepareIntegrity() async {
setState(() => _integrityStatus = 'Preparing...');
try {
await SafetyNetFlutter.prepareIntegrityCheck(_kCloudProjectNumber);
if (!mounted) return;
setState(() => _integrityStatus = 'Prepared — ready to request a token');
} catch (e) {
if (!mounted) return;
setState(() => _integrityStatus = 'Prepare failed: $e');
}
}
Future<void> requestToken() async {
setState(() => _integrityStatus = 'Requesting token...');
try {
final token = await SafetyNetFlutter.requestIntegrityToken(
_kRequestHash,
);
if (!mounted) return;
// In a real app, send `token` to your server for verification —
// never trust a client-side read of it.
setState(() => _integrityStatus = 'Token: ${token.substring(0, 24)}...');
} catch (e) {
if (!mounted) return;
setState(() => _integrityStatus = 'Request failed: $e');
}
}
@override
Widget build(BuildContext context) {
final result = _result;
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('SafetyNet example')),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_error != null) Text('Error: $_error'),
if (result != null) ...[
Text(
result.isCompromised
? 'Compromised device detected'
: 'Device looks clean',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 12),
if (result.reasons.isNotEmpty)
Text('Reasons: ${result.reasons.join(', ')}'),
],
const SizedBox(height: 24),
ElevatedButton(
onPressed: runCheck,
child: const Text('Run check again'),
),
const Divider(height: 48),
Text(
'Play Integrity API (Android-only)',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 12),
if (Platform.isAndroid) ...[
Row(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: prepareIntegrity,
child: const Text('Prepare'),
),
const SizedBox(width: 12),
ElevatedButton(
onPressed: requestToken,
child: const Text('Request token'),
),
],
),
const SizedBox(height: 12),
if (_integrityStatus != null) Text(_integrityStatus!),
] else
const Text('Not available on this platform.'),
],
),
),
),
),
);
}
}