Skip to content

Integrating LIQA into Flutter App

Below is the guide for integrating LIQA into a Flutter application using Dart language.

WebView with LIQA Integrated

Here is a ready-to-use implementation of a WebView with integrated LIQA functionality.

Production Recommendation:

For simplicity, the WebView loads the HTML page from a local string containing the HTML content.

In a real-world application, you may consider loading the page from a CDN managed by your organization. Check the Hosting the HTML Page section for more details.

IMPORTANT

The WebView should be opened over HTTPS. Without HTTPS, the camera access will not be requested, and the camera cannot be used. When hosting locally, you can open the WebView using https://localhost/ as the baseUrl.

dart
liqaWebViewController.loadHtmlString(
  _getHtmlPage(),
  baseUrl: "https://localhost/"
);
liqaWebViewController.loadHtmlString(
  _getHtmlPage(),
  baseUrl: "https://localhost/"
);
dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:permission_handler/permission_handler.dart';
// ignore: depend_on_referenced_packages
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';

class LiqaWebView extends StatefulWidget {
 const LiqaWebView({super.key, required this.onLiqaEvent});

 final Function(String, Map<String, dynamic>?) onLiqaEvent;

 @override
 State<LiqaWebView> createState() => _LiqaWebViewState();
}

class _LiqaWebViewState extends State<LiqaWebView> {
 WebViewController? liqaWebViewController;

 @override
 void initState() {
   super.initState();
   _initializeLiqaWebView();
 }

 @override
 Widget build(BuildContext context) {
   return liqaWebViewController != null
       ? WebViewWidget(
           controller: liqaWebViewController!,
         )
       : const Center(
           child: CircularProgressIndicator(
             color: Colors.blueGrey,
           ),
         );
 }

 Future<void> _initializeLiqaWebView() async {
   // Request camera permission before loading the WebView
   await _requestCameraPermission();

   late final PlatformWebViewControllerCreationParams params;
   if (WebViewPlatform.instance is WebKitWebViewPlatform) {
     params = WebKitWebViewControllerCreationParams(
       allowsInlineMediaPlayback: true,
       mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
     );
   } else {
     params = const PlatformWebViewControllerCreationParams();
   }

   setState(() {
     liqaWebViewController = WebViewController.fromPlatformCreationParams(
       params,
       onPermissionRequest: (request) async {
         // Handle camera permission specifically
         if (request.types.first.name == 'camera') {
           final granted = await _requestCameraPermission();
           if (granted) {
             request.grant();
           } else {
             request.deny();
           }
         }
       },
     )
       ..setJavaScriptMode(JavaScriptMode.unrestricted)
       ..addJavaScriptChannel(
         'FlutterWebView',
         onMessageReceived: (JavaScriptMessage message) {
           try {
             print("Received message from LIQA: ${message.message}");
             final messageObject =
                 jsonDecode(message.message) as Map<String, dynamic>;
             final name = messageObject['name'] as String;
             final payload = messageObject['payload'] as Map<String, dynamic>?;


             // Pass the event to the external handler
             widget.onLiqaEvent(name, payload);
           } catch (e) {
             print('Failed to parse message: $e');
           }
         },
       )
       ..loadHtmlString(_getHtmlPage(), baseUrl: "https://localhost/");
   });
 }

 Future<bool> _requestCameraPermission() async {
   var status = await Permission.camera.status;
   if (!status.isGranted) {
     await Permission.camera.request();
   }


   return status.isGranted;
 }

 String _getHtmlPage() {
   return """
   PUT THE HTML CONTENT HERE
   """;
 }
}
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:permission_handler/permission_handler.dart';
// ignore: depend_on_referenced_packages
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';

class LiqaWebView extends StatefulWidget {
 const LiqaWebView({super.key, required this.onLiqaEvent});

 final Function(String, Map<String, dynamic>?) onLiqaEvent;

 @override
 State<LiqaWebView> createState() => _LiqaWebViewState();
}

class _LiqaWebViewState extends State<LiqaWebView> {
 WebViewController? liqaWebViewController;

 @override
 void initState() {
   super.initState();
   _initializeLiqaWebView();
 }

 @override
 Widget build(BuildContext context) {
   return liqaWebViewController != null
       ? WebViewWidget(
           controller: liqaWebViewController!,
         )
       : const Center(
           child: CircularProgressIndicator(
             color: Colors.blueGrey,
           ),
         );
 }

 Future<void> _initializeLiqaWebView() async {
   // Request camera permission before loading the WebView
   await _requestCameraPermission();

   late final PlatformWebViewControllerCreationParams params;
   if (WebViewPlatform.instance is WebKitWebViewPlatform) {
     params = WebKitWebViewControllerCreationParams(
       allowsInlineMediaPlayback: true,
       mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
     );
   } else {
     params = const PlatformWebViewControllerCreationParams();
   }

   setState(() {
     liqaWebViewController = WebViewController.fromPlatformCreationParams(
       params,
       onPermissionRequest: (request) async {
         // Handle camera permission specifically
         if (request.types.first.name == 'camera') {
           final granted = await _requestCameraPermission();
           if (granted) {
             request.grant();
           } else {
             request.deny();
           }
         }
       },
     )
       ..setJavaScriptMode(JavaScriptMode.unrestricted)
       ..addJavaScriptChannel(
         'FlutterWebView',
         onMessageReceived: (JavaScriptMessage message) {
           try {
             print("Received message from LIQA: ${message.message}");
             final messageObject =
                 jsonDecode(message.message) as Map<String, dynamic>;
             final name = messageObject['name'] as String;
             final payload = messageObject['payload'] as Map<String, dynamic>?;


             // Pass the event to the external handler
             widget.onLiqaEvent(name, payload);
           } catch (e) {
             print('Failed to parse message: $e');
           }
         },
       )
       ..loadHtmlString(_getHtmlPage(), baseUrl: "https://localhost/");
   });
 }

 Future<bool> _requestCameraPermission() async {
   var status = await Permission.camera.status;
   if (!status.isGranted) {
     await Permission.camera.request();
   }


   return status.isGranted;
 }

 String _getHtmlPage() {
   return """
   PUT THE HTML CONTENT HERE
   """;
 }
}

Before integrating the snippet into your app, please ensure that:

  • Camera permissions are requested and granted before loading the WebView.
  • JavaScript is enabled in the WebView (setJavaScriptMode(JavaScriptMode.unrestricted)).
  • WebView loads the page over HTTPS.
  • You have installed the necessary dependencies:
bash
flutter pub add webview_flutter permission_handler
flutter pub add webview_flutter permission_handler

Application Integration Example

Here is an app’s entry point, showcasing a sample integration of LiqaWebView:

dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'liqa_web_view.dart';

void main() {
 runApp(const MyApp());
}

class MyApp extends StatelessWidget {
 const MyApp({super.key});

 @override
 Widget build(BuildContext context) {
   return const MaterialApp(home: HomeScreen());
 }
}

class HomeScreen extends StatefulWidget {
 const HomeScreen({super.key});

 @override
 State<HomeScreen> createState() => _HomeScreenState();
}


class _HomeScreenState extends State<HomeScreen> {
 String? capturedImage;

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: const Text('LIQA Flutter Integration'),
       actions: [
         if (capturedImage != null)
           IconButton(
               onPressed: showCapturedImage, icon: const Icon(Icons.image))
       ],
     ),
     body: LiqaWebView(
       onLiqaEvent: handleLiqaEvent,
     ),
   );
 }

 void handleLiqaEvent(String name, Map<String, dynamic>? payload) {
   print('LIQA Event: $name');
   if (payload != null) {
     print('Payload: ${jsonEncode(payload)}');
   }


   if (name == 'capture') {
     setState(() {
       capturedImage = payload?['data'];
     });
   }
 }

 void showCapturedImage() {
   Navigator.push(
     context,
     MaterialPageRoute(
       builder: (context) => LiqaImageScreen(
         base64String: capturedImage!
             .replaceFirst('data:image/jpeg;base64,', ''),
       ),
     ),
   );
 }
}

class LiqaImageScreen extends StatelessWidget {
 const LiqaImageScreen({super.key, required this.base64String});
 final String base64String;

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: const Text('Captured Image'),
     ),
     body: Image.memory(base64Decode(base64String)),
   );
 }
}
import 'dart:convert';
import 'package:flutter/material.dart';
import 'liqa_web_view.dart';

void main() {
 runApp(const MyApp());
}

class MyApp extends StatelessWidget {
 const MyApp({super.key});

 @override
 Widget build(BuildContext context) {
   return const MaterialApp(home: HomeScreen());
 }
}

class HomeScreen extends StatefulWidget {
 const HomeScreen({super.key});

 @override
 State<HomeScreen> createState() => _HomeScreenState();
}


class _HomeScreenState extends State<HomeScreen> {
 String? capturedImage;

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: const Text('LIQA Flutter Integration'),
       actions: [
         if (capturedImage != null)
           IconButton(
               onPressed: showCapturedImage, icon: const Icon(Icons.image))
       ],
     ),
     body: LiqaWebView(
       onLiqaEvent: handleLiqaEvent,
     ),
   );
 }

 void handleLiqaEvent(String name, Map<String, dynamic>? payload) {
   print('LIQA Event: $name');
   if (payload != null) {
     print('Payload: ${jsonEncode(payload)}');
   }


   if (name == 'capture') {
     setState(() {
       capturedImage = payload?['data'];
     });
   }
 }

 void showCapturedImage() {
   Navigator.push(
     context,
     MaterialPageRoute(
       builder: (context) => LiqaImageScreen(
         base64String: capturedImage!
             .replaceFirst('data:image/jpeg;base64,', ''),
       ),
     ),
   );
 }
}

class LiqaImageScreen extends StatelessWidget {
 const LiqaImageScreen({super.key, required this.base64String});
 final String base64String;

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: const Text('Captured Image'),
     ),
     body: Image.memory(base64Decode(base64String)),
   );
 }
}

Application Permissions Configuration

For LiqaWebView to function correctly, you’ll need to configure camera permissions in your configuration files.

Android

xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

  <!-- Permissions -->
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.CAMERA" />

  <!-- Camera feature -->
  <uses-feature
    android:name="android.hardware.camera"
    android:required="false" />

  <!-- Application Configuration -->
  <application android:label="LIQA WebView Integration">
    <activity
      android:name=".MainActivity"
      android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application>
</manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

  <!-- Permissions -->
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.CAMERA" />

  <!-- Camera feature -->
  <uses-feature
    android:name="android.hardware.camera"
    android:required="false" />

  <!-- Application Configuration -->
  <application android:label="LIQA WebView Integration">
    <activity
      android:name=".MainActivity"
      android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application>
</manifest>

iOS

xml
<key>NSCameraUsageDescription</key>
<string>We need camera access to analyze skin quality.</string>
<key>NSCameraUsageDescription</key>
<string>We need camera access to analyze skin quality.</string>
Podfile
post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)


    target.build_configurations.each do |config|

      config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
        '$(inherited)',


        ## dart: PermissionGroup.camera
        'PERMISSION_CAMERA=1',
      ]


    end
  end
 end
post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)


    target.build_configurations.each do |config|

      config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
        '$(inherited)',


        ## dart: PermissionGroup.camera
        'PERMISSION_CAMERA=1',
      ]


    end
  end
 end

Further Considerations

  • Secure the WebView: Ensure that the WebView is secured against potential vulnerabilities, such as cross-site scripting (XSS).
  • Test Permissions Thoroughly: Verify that the app requests and handles permissions correctly on both iOS and Android.
  • For more details, refer to the Flutter WebView Documentation.

Need Assistance?

If you have any questions regarding the integration or need further assistance, please contact Haut.AI Support Team with this request via the Support Desk.