The codes in this repository are generated by GitHub Copilot Agent
You are a Flutter coding agent. Your task is to build a hybrid mobile application using Flutter with InAppWebView that bridges native functionalities with a WebView-based frontend.
You must implement bi-directional communication between Dart and JavaScript, and support a set of key native features.
For each feature, follow the format:
Task: [Short title]
Instruction:
- [Implementation step 1]
- [Implementation step 2]
- [Tips for integration with JS/native]
Expected Output (Flutter code snippet):
// Example Dart codeTask: Message Bus (JS ↔ Dart)
Instruction:
- Use
addJavaScriptHandlerwith namenativeto receive messages from JS. - From JS, use
window.flutter_inappwebview.callHandler('native', args)to send data. - From Dart, use
evaluateJavascript()to send data to JS. - Add logs for debugging.
Expected Output:
InAppWebView(
onWebViewCreated: (controller) {
controller.addJavaScriptHandler(
handlerName: 'native',
callback: (args) {
print("Received from JS: \$args");
return { 'status': 'OK' };
},
);
controller.evaluateJavascript(
source: "window.postMessageFromFlutter = function(msg) { console.log(msg); }"
);
},
)Task: Push Notifications → JS
Instruction:
- Use
firebase_messagingto listen for push notifications. - When message is received, encode it and send via JS
CustomEvent. - Dispatch to WebView using
evaluateJavascript.
Expected Output:
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
final payload = jsonEncode(message.data);
webViewController.evaluateJavascript(
source: "window.dispatchEvent(new CustomEvent('push', { detail: \$payload }));"
);
});Task: Inject Auth Token into WebView
Instruction:
- Use
flutter_secure_storageto retrieve token. - Inject token into headers when creating
URLRequest. - On token expiration, notify JS to logout.
Expected Output:
final token = await FlutterSecureStorage().read(key: "auth_token");
InAppWebView(
initialUrlRequest: URLRequest(
url: Uri.parse("https://example.com"),
headers: { "Authorization": "Bearer \$token" },
),
)Task: Deep Linking
Instruction:
- Use
uni_linkspackage to handle deep links. - Setup platform-specific configurations in iOS and Android.
- Forward deep link data to WebView using JavaScript bridge.
Expected Output:
@override
void initState() {
super.initState();
initUniLinks();
}
Future<void> initUniLinks() async {
// Handle app start from deep link
Uri? initialLink = await getInitialUri();
if (initialLink != null) {
_handleDeepLink(initialLink);
}
// Handle app in foreground receiving deep link
uriLinkStream.listen((Uri? uri) {
if (uri != null) {
_handleDeepLink(uri);
}
}, onError: (err) {
print('Deep link error: $err');
});
}
void _handleDeepLink(Uri uri) {
final deepLinkData = jsonEncode({
'path': uri.path,
'queryParameters': uri.queryParameters,
});
if (webViewController != null) {
webViewController!.evaluateJavascript(
source: "window.dispatchEvent(new CustomEvent('deepLink', { detail: $deepLinkData }));"
);
} else {
// Store for later when WebView is initialized
_pendingDeepLink = deepLinkData;
}
}Task: File Upload Handler
Instruction:
- Override WebView's file chooser with native file picker.
- Use
file_pickerpackage for native file selection. - Support multiple file selection for different MIME types.
Expected Output:
InAppWebView(
// ...other properties
onReceivedHttpAuthRequest: (controller, challenge) async {
// Handle HTTP authentication
},
onConsoleMessage: (controller, consoleMessage) {
print("JS Console: ${consoleMessage.message}");
},
onCreateWindow: (controller, createWindowAction) async {
// Handle new window creation
return true;
},
onJsAlert: (controller, jsAlertRequest) async {
// Handle JavaScript alerts
return JsAlertResponse(handledByClient: true);
},
onFileChoose: (controller, params) async {
List<String>? paths;
List<PlatformFile>? files;
try {
if (params.allowMultiple) {
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: _parseAcceptTypes(params.acceptTypes),
allowMultiple: true,
);
files = result?.files;
paths = files?.map((file) => file.path!).toList();
} else {
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: _parseAcceptTypes(params.acceptTypes),
);
files = result?.files;
paths = files?.isNotEmpty == true ? [files!.first.path!] : null;
}
return FileChooserResponse(
paths: paths,
handledByClient: true,
);
} catch (e) {
print("File picking error: $e");
return FileChooserResponse(handledByClient: false);
}
},
)
List<String> _parseAcceptTypes(List<String>? acceptTypes) {
if (acceptTypes == null || acceptTypes.isEmpty) {
return [];
}
final extensions = <String>[];
for (final type in acceptTypes) {
if (type.startsWith('.')) {
extensions.add(type.substring(1));
} else if (type.contains('/')) {
// Handle MIME types like 'image/*'
final parts = type.split('/');
if (parts.length == 2 && parts[1] == '*') {
switch (parts[0]) {
case 'image':
extensions.addAll(['jpg', 'jpeg', 'png', 'gif', 'webp']);
break;
case 'video':
extensions.addAll(['mp4', 'mov', 'avi', 'mkv']);
break;
case 'audio':
extensions.addAll(['mp3', 'wav', 'ogg', 'aac']);
break;
}
}
}
}
return extensions;
}Task: Geolocation Access
Instruction:
- Use
geolocatorpackage to access device location. - Create JavaScript handler for location requests.
- Handle permission requests and error states.
Expected Output:
// Add permission handlers in main app
Future<void> requestLocationPermission() async {
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
}
}
// In WebView setup
void setupLocationBridge(InAppWebViewController controller) {
controller.addJavaScriptHandler(
handlerName: 'getLocation',
callback: (args) async {
try {
// Check if high accuracy is requested
bool highAccuracy = args.isNotEmpty && args[0] == true;
final position = await Geolocator.getCurrentPosition(
desiredAccuracy: highAccuracy ?
LocationAccuracy.high : LocationAccuracy.medium
);
return {
'status': 'OK',
'latitude': position.latitude,
'longitude': position.longitude,
'accuracy': position.accuracy,
'altitude': position.altitude,
'speed': position.speed,
'timestamp': position.timestamp?.millisecondsSinceEpoch,
};
} catch (e) {
print("Location error: $e");
return {
'status': 'ERROR',
'message': e.toString(),
};
}
},
);
// Inject JS helper function
controller.evaluateJavascript(source: '''
window.getNativeLocation = function(highAccuracy = false) {
return window.flutter_inappwebview.callHandler('getLocation', highAccuracy);
};
''');
}Task: Lifecycle Synchronization
Instruction:
- Override app lifecycle methods.
- Notify WebView of app state changes using JavaScript events.
- Pause and resume WebView resources accordingly.
Expected Output:
class _WebViewScreenState extends State<WebViewScreen> with WidgetsBindingObserver {
InAppWebViewController? _webViewController;
AppLifecycleState? _lastState;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
_lastState = state;
if (_webViewController == null) return;
switch (state) {
case AppLifecycleState.resumed:
_webViewController!.evaluateJavascript(
source: "window.dispatchEvent(new CustomEvent('appLifecycle', { detail: { state: 'resumed' } }));"
);
_webViewController!.resume();
break;
case AppLifecycleState.paused:
_webViewController!.evaluateJavascript(
source: "window.dispatchEvent(new CustomEvent('appLifecycle', { detail: { state: 'paused' } }));"
);
_webViewController!.pause();
break;
case AppLifecycleState.inactive:
_webViewController!.evaluateJavascript(
source: "window.dispatchEvent(new CustomEvent('appLifecycle', { detail: { state: 'inactive' } }));"
);
break;
case AppLifecycleState.detached:
_webViewController!.evaluateJavascript(
source: "window.dispatchEvent(new CustomEvent('appLifecycle', { detail: { state: 'detached' } }));"
);
break;
}
}
}Task: Clipboard Access
Instruction:
- Use
flutter_clipboardpackage to access device clipboard. - Create JavaScript handlers for read/write operations.
- Handle permissions and error states.
Expected Output:
void setupClipboardBridge(InAppWebViewController controller) {
// Handler to read from clipboard
controller.addJavaScriptHandler(
handlerName: 'readClipboard',
callback: (args) async {
try {
final data = await Clipboard.getData(Clipboard.kTextPlain);
return {
'status': 'OK',
'text': data?.text ?? '',
};
} catch (e) {
print("Clipboard read error: $e");
return {
'status': 'ERROR',
'message': e.toString(),
};
}
},
);
// Handler to write to clipboard
controller.addJavaScriptHandler(
handlerName: 'writeClipboard',
callback: (args) async {
try {
if (args.isEmpty || args[0] == null) {
return {
'status': 'ERROR',
'message': 'No text provided',
};
}
await Clipboard.setData(ClipboardData(text: args[0].toString()));
return {
'status': 'OK',
};
} catch (e) {
print("Clipboard write error: $e");
return {
'status': 'ERROR',
'message': e.toString(),
};
}
},
);
// Inject JS helper functions
controller.evaluateJavascript(source: '''
window.nativeClipboard = {
readText: function() {
return window.flutter_inappwebview.callHandler('readClipboard');
},
writeText: function(text) {
return window.flutter_inappwebview.callHandler('writeClipboard', text);
}
};
''');
}Task: Error Logging and Crash Reporting
Instruction:
- Use
firebase_crashlyticsfor error tracking. - Add JavaScript console interceptor for web errors.
- Implement global error boundary in Dart.
Expected Output:
// Setup in main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Pass all uncaught errors to Crashlytics
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
runZonedGuarded<Future<void>>(() async {
runApp(MyApp());
}, (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
});
}
// In WebView class
void setupErrorLogging(InAppWebViewController controller) {
// Log console messages to Crashlytics
controller.onConsoleMessage = (controller, consoleMessage) {
print("WebView Console: ${consoleMessage.message}");
// Log errors to Crashlytics
if (consoleMessage.messageLevel == ConsoleMessageLevel.ERROR) {
FirebaseCrashlytics.instance.recordError(
"WebView JS Error: ${consoleMessage.message}",
StackTrace.current,
reason: "JavaScript Console Error",
);
}
};
// Inject JS error handler
controller.evaluateJavascript(source: '''
window.addEventListener('error', function(event) {
window.flutter_inappwebview.callHandler(
'jsError',
{
message: event.message,
source: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error ? event.error.toString() : null
}
);
// Don't prevent default handling
return false;
});
''');
// Handle JS errors in Dart
controller.addJavaScriptHandler(
handlerName: 'jsError',
callback: (args) {
if (args.isNotEmpty && args[0] is Map) {
final errorData = args[0] as Map;
FirebaseCrashlytics.instance.recordError(
"JavaScript Error: ${errorData['message']}",
StackTrace.current,
reason: "JS Error at ${errorData['source']}:${errorData['lineno']}:${errorData['colno']}",
);
}
return {'logged': true};
},
);
}