افزودن WebView به برنامه Flutter خود

1. مقدمه

آخرین به روز رسانی: 2021-10-19

با افزونه WebView Flutter می توانید ویجت WebView را به برنامه Flutter اندروید یا iOS خود اضافه کنید. در iOS ویجت WebView توسط یک WKWebView پشتیبانی می شود، در حالی که در Android ویجت WebView توسط یک WebView پشتیبانی می شود. این افزونه می تواند ویجت های Flutter را در نمای وب ارائه دهد. بنابراین به عنوان مثال می توان یک منوی کشویی را در نمای وب ارائه کرد.

چیزی که خواهی ساخت

در این کد لبه، شما یک برنامه تلفن همراه را مرحله به مرحله می‌سازید که دارای WebView با استفاده از Flutter SDK است. برنامه شما:

  • نمایش محتوای وب در WebView
  • ویجت‌های Flutter را که روی WebView انباشته شده‌اند نمایش دهید
  • به رویدادهای پیشرفت بارگذاری صفحه واکنش نشان دهید
  • WebView از طریق WebViewController کنترل کنید
  • وب سایت ها را با استفاده از NavigationDelegate مسدود کنید
  • عبارات جاوا اسکریپت را ارزیابی کنید
  • با JavascriptChannels ، تماس های جاوا اسکریپت را مدیریت کنید
  • تنظیم، حذف، افزودن یا نمایش کوکی ها
  • HTML را از دارایی ها، فایل ها یا رشته های حاوی HTML بارگیری و نمایش دهید

تصویری از یک شبیه‌ساز آیفون در حال اجرای برنامه Flutter با نمای وب تعبیه‌شده که صفحه اصلی Flutter.dev را نشان می‌دهد.

تصویری از یک شبیه‌ساز اندرویدی که یک برنامه Flutter را با نمای وب تعبیه‌شده اجرا می‌کند که صفحه اصلی Flutter.dev را نشان می‌دهد.

چیزی که یاد خواهید گرفت

در این لبه کد یاد خواهید گرفت که چگونه از پلاگین webview_flutter به روش های مختلفی استفاده کنید، از جمله:

  • نحوه پیکربندی افزونه webview_flutter
  • نحوه گوش دادن به رویدادهای پیشرفت بارگذاری صفحه
  • نحوه کنترل پیمایش صفحه
  • چگونه به WebView دستور دهیم که در طول تاریخچه خود به عقب و جلو برود
  • نحوه ارزیابی جاوا اسکریپت، از جمله استفاده از نتایج بازگشتی
  • نحوه ثبت تماس برای فراخوانی کد دارت از جاوا اسکریپت
  • نحوه مدیریت کوکی ها
  • نحوه بارگذاری و نمایش صفحات HTML از دارایی ها یا فایل ها یا رشته های حاوی HTML

آنچه شما نیاز دارید

2. محیط توسعه Flutter خود را تنظیم کنید

برای تکمیل این آزمایشگاه به دو نرم افزار نیاز دارید - Flutter SDK و یک ویرایشگر .

شما می توانید کدلب را با استفاده از هر یک از این دستگاه ها اجرا کنید:

  • یک دستگاه فیزیکی Android یا iOS که به رایانه شما متصل شده و روی حالت Developer تنظیم شده است.
  • شبیه ساز iOS (نیاز به نصب ابزار Xcode دارد).
  • شبیه ساز اندروید (نیاز به نصب در Android Studio دارد).

3. شروع به کار

شروع کار با فلاتر

راه های مختلفی برای ایجاد یک پروژه فلاتر جدید وجود دارد که هم Android Studio و هم Visual Studio Code ابزارهایی برای این کار ارائه می دهند. یا رویه های مرتبط را برای ایجاد یک پروژه دنبال کنید، یا دستورات زیر را در ترمینال خط فرمان مفید اجرا کنید.

$ flutter create --platforms=android,ios webview_in_flutter
Creating project webview_in_flutter...
Resolving dependencies in `webview_in_flutter`... 
Downloading packages... 
Got dependencies in `webview_in_flutter`.
Wrote 74 files.

All done!
You can find general documentation for Flutter at: https://docs.flutter.dev/
Detailed API documentation is available at: https://api.flutter.dev/
If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev

In order to run your application, type:

  $ cd webview_in_flutter
  $ flutter run

Your application code is in webview_in_flutter/lib/main.dart.

افزودن افزونه WebView Flutter به عنوان یک وابستگی

افزودن قابلیت اضافی به برنامه Flutter با استفاده از بسته‌های Pub آسان است. در این کد لبه پلاگین webview_flutter را به پروژه خود اضافه خواهید کرد. دستورات زیر را در ترمینال اجرا کنید.

$ cd webview_in_flutter
$ flutter pub add webview_flutter
Resolving dependencies...
Downloading packages...
  collection 1.18.0 (1.19.0 available)
  leak_tracker 10.0.5 (10.0.7 available)
  leak_tracker_flutter_testing 3.0.5 (3.0.7 available)
  material_color_utilities 0.11.1 (0.12.0 available)
+ plugin_platform_interface 2.1.8
  string_scanner 1.2.0 (1.3.0 available)
  test_api 0.7.2 (0.7.3 available)
+ webview_flutter 4.9.0
+ webview_flutter_android 3.16.7
+ webview_flutter_platform_interface 2.10.0
+ webview_flutter_wkwebview 3.15.0
Changed 5 dependencies!
6 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.

اگر pubspec.yaml خود را بررسی کنید، اکنون خواهید دید که یک خط در بخش وابستگی ها برای افزونه webview_flutter دارد.

MinSDK اندروید را پیکربندی کنید

برای استفاده از افزونه webview_flutter در اندروید باید minSDK روی 20 تنظیم کنید. فایل android/app/build.gradle خود را به صورت زیر تغییر دهید:

android/app/build.gradle

android {
    //...

    defaultConfig {
        applicationId = "com.example.webview_in_flutter"
        minSdk = 20                                         // Modify this line
        targetSdk = flutter.targetSdkVersion
        versionCode = flutterVersionCode.toInteger()
        versionName = flutterVersionName
    }

4. افزودن ویجت WebView به برنامه Flutter

در این مرحله یک WebView به برنامه خود اضافه می کنید. WebView ها نماهای بومی میزبانی می شوند و شما به عنوان یک توسعه دهنده برنامه می توانید نحوه میزبانی این نماهای بومی را در برنامه خود انتخاب کنید. در اندروید شما می توانید بین نمایشگرهای مجازی، که در حال حاضر پیش فرض برای اندروید است، و ترکیب ترکیبی انتخاب کنید. با این حال، iOS همیشه از ترکیب ترکیبی استفاده می کند.

برای بحث عمیق در مورد تفاوت‌های بین نمایشگرهای مجازی و ترکیب ترکیبی، لطفاً مستندات مربوط به میزبانی نماهای بومی Android و iOS را در برنامه Flutter خود با پلتفرم Views مطالعه کنید.

قرار دادن نمای وب روی صفحه

محتویات lib/main.dart را با موارد زیر جایگزین کنید:

lib/main.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(useMaterial3: true),
      home: const WebViewApp(),
    ),
  );
}

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

  @override
  State<WebViewApp> createState() => _WebViewAppState();
}

class _WebViewAppState extends State<WebViewApp> {
  late final WebViewController controller;

  @override
  void initState() {
    super.initState();
    controller = WebViewController()
      ..loadRequest(
        Uri.parse('https://flutter.dev'),
      );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter WebView'),
      ),
      body: WebViewWidget(
        controller: controller,
      ),
    );
  }
}

با اجرای این برنامه در iOS یا Android، یک WebView به عنوان یک پنجره مرورگر کامل در دستگاه شما نشان داده می شود، به این معنی که مرورگر در دستگاه شما به صورت تمام صفحه بدون هیچ گونه حاشیه یا حاشیه نشان داده می شود. با اسکرول کردن، متوجه قسمت هایی از صفحه می شوید که ممکن است کمی عجیب به نظر برسند. این به این دلیل است که جاوا اسکریپت در حال حاضر غیرفعال است و رندر کردن flutter.dev به درستی به جاوا اسکریپت نیاز دارد.

اجرای برنامه

برنامه Flutter را در iOS یا Android اجرا کنید تا یک Webview را ببینید که وب سایت flutter.dev را نمایش می دهد. یا برنامه را در شبیه ساز اندروید یا شبیه ساز iOS اجرا کنید. به راحتی می توانید URL اولیه WebView را با وب سایت خود جایگزین کنید.

$ flutter run

با فرض اینکه شبیه ساز یا شبیه ساز مناسب را در حال اجرا دارید یا یک دستگاه فیزیکی متصل شده اید، پس از کامپایل و استقرار برنامه در دستگاه خود، باید چیزی شبیه به زیر مشاهده کنید:

تصویری از یک شبیه‌ساز آیفون در حال اجرای برنامه Flutter با نمای وب تعبیه‌شده که صفحه اصلی Flutter.dev را نشان می‌دهد.

تصویری از یک شبیه‌ساز اندرویدی که یک برنامه Flutter را با نمای وب تعبیه‌شده اجرا می‌کند که صفحه اصلی Flutter.dev را نشان می‌دهد.

5. گوش دادن به رویدادهای بارگذاری صفحه

ویجت WebView چندین رویداد پیشرفت بارگذاری صفحه را ارائه می دهد که برنامه شما می تواند به آنها گوش دهد. در طول چرخه بارگذاری صفحه WebView ، سه رویداد بارگیری صفحه متفاوت وجود دارد: onPageStarted ، onProgress و onPageFinished . در این مرحله شما یک نشانگر بارگذاری صفحه را پیاده سازی می کنید. به عنوان یک امتیاز، این نشان می دهد که می توانید محتوای Flutter را در قسمت محتوای WebView ارائه دهید.

افزودن رویدادهای بارگذاری صفحه به برنامه شما

یک فایل منبع جدید در lib/src/web_view_stack.dart ایجاد کنید و آن را با محتوای زیر پر کنید:

lib/src/web_view_stack.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

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

  @override
  State<WebViewStack> createState() => _WebViewStackState();
}

class _WebViewStackState extends State<WebViewStack> {
  var loadingPercentage = 0;
  late final WebViewController controller;

  @override
  void initState() {
    super.initState();
    controller = WebViewController()
      ..setNavigationDelegate(NavigationDelegate(
        onPageStarted: (url) {
          setState(() {
            loadingPercentage = 0;
          });
        },
        onProgress: (progress) {
          setState(() {
            loadingPercentage = progress;
          });
        },
        onPageFinished: (url) {
          setState(() {
            loadingPercentage = 100;
          });
        },
      ))
      ..loadRequest(
        Uri.parse('https://flutter.dev'),
      );
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        WebViewWidget(
          controller: controller,
        ),
        if (loadingPercentage < 100)
          LinearProgressIndicator(
            value: loadingPercentage / 100.0,
          ),
      ],
    );
  }
}

این کد ویجت WebView را در یک Stack پیچیده کرده است، و به طور مشروط WebView با یک LinearProgressIndicator زمانی که درصد بارگذاری صفحه کمتر از 100٪ است، پوشش داده است. از آنجایی که این شامل حالت برنامه است که در طول زمان تغییر می کند، شما این حالت را در یک کلاس State مرتبط با StatefulWidget ذخیره کرده اید.

برای استفاده از این ویجت جدید WebViewStack ، lib/main.dart خود را به صورت زیر تغییر دهید:

lib/main.dart

import 'package:flutter/material.dart';

import 'src/web_view_stack.dart';

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(useMaterial3: true),
      home: const WebViewApp(),
    ),
  );
}

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

  @override
  State<WebViewApp> createState() => _WebViewAppState();
}

class _WebViewAppState extends State<WebViewApp> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter WebView'),
      ),
      body: const WebViewStack(),
    );
  }
}

وقتی برنامه را اجرا می‌کنید، بسته به شرایط شبکه‌تان، و اینکه آیا مرورگر صفحه‌ای را که در آن پیمایش می‌کنید ذخیره کرده است یا خیر، یک نشانگر بارگذاری صفحه را می‌بینید که در بالای قسمت محتوای WebView قرار گرفته است.

6. کار با WebViewController

دسترسی به WebViewController از ویجت WebView

ویجت WebView کنترل برنامه ای را با یک WebViewController فعال می کند. این کنترلر پس از ساخت ویجت WebView از طریق یک callback در دسترس قرار می گیرد. ماهیت ناهمزمان در دسترس بودن این کنترلر، آن را به یک کاندیدای اصلی برای کلاس Completer<T> دارت تبدیل می کند.

lib/src/web_view_stack.dart را به صورت زیر به روز کنید:

lib/src/web_view_stack.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class WebViewStack extends StatefulWidget {
  const WebViewStack({required this.controller, super.key}); // MODIFY

  final WebViewController controller;                        // ADD

  @override
  State<WebViewStack> createState() => _WebViewStackState();
}

class _WebViewStackState extends State<WebViewStack> {
  var loadingPercentage = 0;
  // REMOVE the controller that was here

  @override
  void initState() {
    super.initState();
    // Modify from here...
    widget.controller.setNavigationDelegate(
      NavigationDelegate(
        onPageStarted: (url) {
          setState(() {
            loadingPercentage = 0;
          });
        },
        onProgress: (progress) {
          setState(() {
            loadingPercentage = progress;
          });
        },
        onPageFinished: (url) {
          setState(() {
            loadingPercentage = 100;
          });
        },
      ),
    );
    // ...to here.
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        WebViewWidget(
          controller: widget.controller,                     // MODIFY
        ),
        if (loadingPercentage < 100)
          LinearProgressIndicator(
            value: loadingPercentage / 100.0,
          ),
      ],
    );
  }
}

ویجت WebViewStack اکنون از یک کنترلر ایجاد شده در ویجت اطراف استفاده می کند. این کار کنترلر WebViewWidget را قادر می سازد تا به راحتی با سایر بخش های برنامه به اشتراک گذاشته شود.

ساخت کنترل های ناوبری

داشتن یک WebView کار یک چیز است، اما توانایی پیمایش به عقب و جلو در تاریخچه صفحه، و بارگذاری مجدد صفحه، مجموعه مفیدی از اضافات خواهد بود. خوشبختانه، با یک WebViewController می توانید این قابلیت را به برنامه خود اضافه کنید.

یک فایل منبع جدید در lib/src/navigation_controls.dart ایجاد کنید و آن را با موارد زیر پر کنید:

lib/src/navigation_controls.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class NavigationControls extends StatelessWidget {
  const NavigationControls({required this.controller, super.key});

  final WebViewController controller;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        IconButton(
          icon: const Icon(Icons.arrow_back_ios),
          onPressed: () async {
            final messenger = ScaffoldMessenger.of(context);
            if (await controller.canGoBack()) {
              await controller.goBack();
            } else {
              messenger.showSnackBar(
                const SnackBar(content: Text('No back history item')),
              );
              return;
            }
          },
        ),
        IconButton(
          icon: const Icon(Icons.arrow_forward_ios),
          onPressed: () async {
            final messenger = ScaffoldMessenger.of(context);
            if (await controller.canGoForward()) {
              await controller.goForward();
            } else {
              messenger.showSnackBar(
                const SnackBar(content: Text('No forward history item')),
              );
              return;
            }
          },
        ),
        IconButton(
          icon: const Icon(Icons.replay),
          onPressed: () {
            controller.reload();
          },
        ),
      ],
    );
  }
}

این ویجت از WebViewController به اشتراک گذاشته شده با آن در زمان ساخت استفاده می کند تا کاربر بتواند WebView از طریق یک سری IconButton کنترل کند.

افزودن کنترل های ناوبری به AppBar

با در دست داشتن WebViewStack به‌روز شده و NavigationControls که به تازگی ساخته شده‌اند، اکنون زمان آن فرا رسیده است که همه آن‌ها را در یک WebViewApp به‌روز شده قرار دهید. اینجاست که ما WebViewController مشترک را می سازیم. با داشتن WebViewApp در نزدیکی بالای درخت ویجت در این برنامه، ایجاد آن در این سطح منطقی است.

فایل lib/main.dart را به صورت زیر به روز کنید:

lib/main.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';  // ADD

import 'src/navigation_controls.dart';                  // ADD
import 'src/web_view_stack.dart';

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(useMaterial3: true),
      home: const WebViewApp(),
    ),
  );
}

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

  @override
  State<WebViewApp> createState() => _WebViewAppState();
}

class _WebViewAppState extends State<WebViewApp> {
  // Add from here...
  late final WebViewController controller;

  @override
  void initState() {
    super.initState();
    controller = WebViewController()
      ..loadRequest(
        Uri.parse('https://flutter.dev'),
      );
  }
  // ...to here.

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter WebView'),
        // Add from here...
        actions: [
          NavigationControls(controller: controller),
        ],
        // ...to here.
      ),
      body: WebViewStack(controller: controller),       // MODIFY
    );
  }
}

اجرای برنامه باید یک صفحه وب با کنترل های زیر را نشان دهد:

تصویری از یک شبیه‌ساز آیفون در حال اجرای برنامه Flutter با نمای وب تعبیه‌شده که صفحه اصلی Flutter.dev را با کنترل‌های صفحه قبلی، صفحه بعدی و بارگذاری مجدد صفحه نشان می‌دهد.

تصویری از یک شبیه‌ساز Android که یک برنامه Flutter را با نمای وب تعبیه‌شده اجرا می‌کند که صفحه اصلی Flutter.dev را با کنترل‌های صفحه قبلی، صفحه بعدی و بارگذاری مجدد صفحه نشان می‌دهد.

7. پیگیری ناوبری با NavigationDelegate

WebView به برنامه شما یک NavigationDelegate, ارائه می دهد که به برنامه شما امکان می دهد تا پیمایش صفحه ویجت WebView را ردیابی و کنترل کند. هنگامی که یک ناوبری توسط WebView, برای مثال زمانی که کاربر روی یک پیوند کلیک می کند، NavigationDelegate فراخوانی می شود. از NavigationDelegate می توان برای کنترل اینکه آیا WebView با پیمایش ادامه می یابد یا خیر، استفاده کرد.

یک NavigationDelegate سفارشی ثبت کنید

در این مرحله، برای مسدود کردن پیمایش در YouTube.com ، یک پاسخ تماس NavigationDelegate ثبت می‌کنید. توجه داشته باشید، این پیاده سازی ساده همچنین محتوای درون خطی YouTube را که در صفحات مختلف اسناد Flutter API ظاهر می شود، مسدود می کند.

lib/src/web_view_stack.dart را به صورت زیر به روز کنید:

lib/src/web_view_stack.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class WebViewStack extends StatefulWidget {
  const WebViewStack({required this.controller, super.key});

  final WebViewController controller;

  @override
  State<WebViewStack> createState() => _WebViewStackState();
}

class _WebViewStackState extends State<WebViewStack> {
  var loadingPercentage = 0;

  @override
  void initState() {
    super.initState();
    widget.controller.setNavigationDelegate(
      NavigationDelegate(
        onPageStarted: (url) {
          setState(() {
            loadingPercentage = 0;
          });
        },
        onProgress: (progress) {
          setState(() {
            loadingPercentage = progress;
          });
        },
        onPageFinished: (url) {
          setState(() {
            loadingPercentage = 100;
          });
        },
        // Add from here...
        onNavigationRequest: (navigation) {
          final host = Uri.parse(navigation.url).host;
          if (host.contains('youtube.com')) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text(
                  'Blocking navigation to $host',
                ),
              ),
            );
            return NavigationDecision.prevent;
          }
          return NavigationDecision.navigate;
        },
        // ...to here.
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        WebViewWidget(
          controller: widget.controller,
        ),
        if (loadingPercentage < 100)
          LinearProgressIndicator(
            value: loadingPercentage / 100.0,
          ),
      ],
    );
  }
}

در مرحله بعد، یک آیتم منو اضافه می کنید تا با استفاده از کلاس WebViewController ، تست NavigationDelegate خود را فعال کنید. به عنوان تمرینی برای خواننده باقی می‌ماند تا منطق پاسخ به تماس را تقویت کند تا فقط ناوبری صفحه کامل به YouTube.com را مسدود کند و همچنان محتوای YouTube درون‌خط در اسناد API را مجاز کند.

8. افزودن دکمه منو به AppBar

در طی چند مرحله بعدی، یک دکمه منو در ویجت AppBar ایجاد خواهید کرد که برای ارزیابی جاوا اسکریپت، فراخوانی کانال های جاوا اسکریپت و مدیریت کوکی ها استفاده می شود. در مجموع، منوی مفیدی است.

یک فایل منبع جدید در lib/src/menu.dart ایجاد کنید و آن را با موارد زیر پر کنید:

lib/src/menu.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

enum _MenuOptions {
  navigationDelegate,
}

class Menu extends StatelessWidget {
  const Menu({required this.controller, super.key});

  final WebViewController controller;

  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<_MenuOptions>(
      onSelected: (value) async {
        switch (value) {
          case _MenuOptions.navigationDelegate:
            await controller.loadRequest(Uri.parse('https://youtube.com'));
        }
      },
      itemBuilder: (context) => [
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.navigationDelegate,
          child: Text('Navigate to YouTube'),
        ),
      ],
    );
  }
}

هنگامی که کاربر گزینه منوی Navigate to YouTube را انتخاب می کند، روش loadRequest WebViewController اجرا می شود. این پیمایش توسط navigationDelegate callback که در مرحله قبل ایجاد کردید مسدود خواهد شد.

برای افزودن منو به صفحه WebViewApp ، lib/main.dart را به صورت زیر تغییر دهید:

lib/main.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

import 'src/menu.dart';                               // ADD
import 'src/navigation_controls.dart';
import 'src/web_view_stack.dart';

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(useMaterial3: true),
      home: const WebViewApp(),
    ),
  );
}

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

  @override
  State<WebViewApp> createState() => _WebViewAppState();
}

class _WebViewAppState extends State<WebViewApp> {
  late final WebViewController controller;

  @override
  void initState() {
    super.initState();
    controller = WebViewController()
      ..loadRequest(
        Uri.parse('https://flutter.dev'),
      );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter WebView'),
        actions: [
          NavigationControls(controller: controller),
          Menu(controller: controller),               // ADD
        ],
      ),
      body: WebViewStack(controller: controller),
    );
  }
}

برنامه خود را اجرا کنید و روی آیتم منوی Navigate to YouTube ضربه بزنید. باید با SnackBar به شما سلام شود که به شما اطلاع می‌دهد که کنترل‌کننده ناوبری مسیریابی به YouTube را مسدود کرده است.

تصویری از یک شبیه‌ساز اندرویدی که یک برنامه Flutter را با نمای وب تعبیه‌شده اجرا می‌کند که صفحه اصلی Flutter.dev را با یک آیتم منو نشان می‌دهد که گزینه «پیمایش به YouTube» را نشان می‌دهد.

تصویری از یک شبیه‌ساز Android در حال اجرای برنامه Flutter با نمای وب تعبیه‌شده که صفحه اصلی Flutter.dev را با نان تست با خواندن «مسدود کردن پیمایش به m.youtube.com» نشان می‌دهد.

9. ارزیابی جاوا اسکریپت

WebViewController می تواند عبارات جاوا اسکریپت را در زمینه صفحه فعلی ارزیابی کند. دو روش مختلف برای ارزیابی جاوا اسکریپت وجود دارد: برای کد جاوا اسکریپتی که مقداری را بر نمی گرداند، از runJavaScript استفاده کنید و برای کد جاوا اسکریپتی که مقداری را برمی گرداند، از runJavaScriptReturningResult استفاده کنید.

برای فعال کردن جاوا اسکریپت، باید WebViewController را با ویژگی javaScriptMode که روی JavascriptMode.unrestricted تنظیم شده است، پیکربندی کنید. به طور پیش فرض، javascriptMode روی JavascriptMode.disabled تنظیم شده است.

کلاس _WebViewStackState را با افزودن تنظیمات javascriptMode به صورت زیر به روز کنید:

lib/src/web_view_stack.dart

class _WebViewStackState extends State<WebViewStack> {
  var loadingPercentage = 0;

  @override
  void initState() {
    super.initState();
    widget.controller
      ..setNavigationDelegate(              // Modify this line to use .. instead of .
        NavigationDelegate(
          onPageStarted: (url) {
            setState(() {
              loadingPercentage = 0;
            });
          },
          onProgress: (progress) {
            setState(() {
              loadingPercentage = progress;
            });
          },
          onPageFinished: (url) {
            setState(() {
              loadingPercentage = 100;
            });
          },
          onNavigationRequest: (navigation) {
            final host = Uri.parse(navigation.url).host;
            if (host.contains('youtube.com')) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text(
                    'Blocking navigation to $host',
                  ),
                ),
              );
              return NavigationDecision.prevent;
            }
            return NavigationDecision.navigate;
          },
        ),
      )
      ..setJavaScriptMode(JavaScriptMode.unrestricted);        // Add this line
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        WebViewWidget(
          controller: widget.controller,
        ),
        if (loadingPercentage < 100)
          LinearProgressIndicator(
            value: loadingPercentage / 100.0,
          ),
      ],
    );
  }
}

اکنون که WebViewWidget می تواند جاوا اسکریپت را اجرا کند، می توانید گزینه ای برای استفاده از روش runJavaScriptReturningResult به منو اضافه کنید.

با استفاده از ویرایشگر یا برخی از کارهای صفحه کلید، کلاس Menu را به StatefulWidget تبدیل کنید. lib/src/menu.dart را برای مطابقت با موارد زیر تغییر دهید:

lib/src/menu.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

enum _MenuOptions {
  navigationDelegate,
  userAgent,                                              // Add this line
}

class Menu extends StatefulWidget {                       // Convert to StatefulWidget
  const Menu({required this.controller, super.key});

  final WebViewController controller;

  @override                                               // Add from here
  State<Menu> createState() => _MenuState();
}

class _MenuState extends State<Menu> {                    // To here.
  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<_MenuOptions>(
      onSelected: (value) async {
        switch (value) {
          case _MenuOptions.navigationDelegate:           // Modify from here
            await widget.controller
                .loadRequest(Uri.parse('https://youtube.com'));
          case _MenuOptions.userAgent:
            final userAgent = await widget.controller
                .runJavaScriptReturningResult('navigator.userAgent');
            if (!context.mounted) return;
            ScaffoldMessenger.of(context).showSnackBar(SnackBar(
              content: Text('$userAgent'),
            ));                                           // To here.
        }
      },
      itemBuilder: (context) => [
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.navigationDelegate,
          child: Text('Navigate to YouTube'),
        ),
        const PopupMenuItem<_MenuOptions>(                // Add from here
          value: _MenuOptions.userAgent,
          child: Text('Show user-agent'),
        ),                                                // To here.
      ],
    );
  }
}

وقتی روی گزینه منوی «نمایش عامل کاربر» ضربه می‌زنید، نتیجه اجرای عبارت JavaScript navigator.userAgent در یک Snackbar نشان داده می‌شود. هنگام اجرای برنامه، ممکن است متوجه شوید که صفحه Flutter.dev متفاوت به نظر می رسد. این نتیجه اجرای با جاوا اسکریپت فعال است.

تصویری از یک شبیه‌ساز آیفون در حال اجرا برنامه Flutter با نمای وب تعبیه‌شده که صفحه اصلی Flutter.dev را با آیتم‌های منو نشان می‌دهد که گزینه‌های «Navigate to YouTube» یا «Show user-agent» را نشان می‌دهد.

تصویری از یک شبیه‌ساز آیفون در حال اجرای برنامه Flutter با نمای وب تعبیه‌شده که صفحه اصلی Flutter.dev را به همراه نان تست نشان می‌دهد که رشته عامل کاربر را نشان می‌دهد.

10. کار با کانال های جاوا اسکریپت

کانال‌های جاوا اسکریپت برنامه شما را قادر می‌سازد تا کنترل‌کننده‌های پاسخ به تماس را در زمینه جاوا اسکریپت WebViewWidget ثبت کند که می‌تواند برای انتقال مقادیر به کد Dart برنامه استفاده شود. در این مرحله شما یک کانال SnackBar را ثبت می کنید که با نتیجه XMLHttpRequest فراخوانی می شود.

کلاس WebViewStack را به صورت زیر به روز کنید:

lib/src/web_view_stack.dart

class WebViewStack extends StatefulWidget {
  const WebViewStack({required this.controller, super.key});

  final WebViewController controller;

  @override
  State<WebViewStack> createState() => _WebViewStackState();
}

class _WebViewStackState extends State<WebViewStack> {
  var loadingPercentage = 0;

  @override
  void initState() {
    super.initState();
    widget.controller
      ..setNavigationDelegate(
        NavigationDelegate(
          onPageStarted: (url) {
            setState(() {
              loadingPercentage = 0;
            });
          },
          onProgress: (progress) {
            setState(() {
              loadingPercentage = progress;
            });
          },
          onPageFinished: (url) {
            setState(() {
              loadingPercentage = 100;
            });
          },
          onNavigationRequest: (navigation) {
            final host = Uri.parse(navigation.url).host;
            if (host.contains('youtube.com')) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text(
                    'Blocking navigation to $host',
                  ),
                ),
              );
              return NavigationDecision.prevent;
            }
            return NavigationDecision.navigate;
          },
        ),
      )
      // Modify from here...
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..addJavaScriptChannel(
        'SnackBar',
        onMessageReceived: (message) {
          ScaffoldMessenger.of(context)
              .showSnackBar(SnackBar(content: Text(message.message)));
        },
      );
      // ...to here.
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        WebViewWidget(
          controller: widget.controller,
        ),
        if (loadingPercentage < 100)
          LinearProgressIndicator(
            value: loadingPercentage / 100.0,
          ),
      ],
    );
  }
}

برای هر کانال جاوا اسکریپت در Set ، یک شی کانال در زمینه جاوا اسکریپت به عنوان یک ویژگی پنجره با نام name کانال جاوا اسکریپت در دسترس است. استفاده از این مورد از زمینه جاوا اسکریپت شامل فراخوانی postMessage در کانال جاوا اسکریپت برای ارسال پیامی است که به کنترل کننده پاسخ تماس onMessageReceived در JavascriptChannel ارسال می شود.

برای استفاده از کانال جاوا اسکریپت اضافه شده در بالا، آیتم منو دیگری اضافه کنید که یک XMLHttpRequest را در زمینه جاوا اسکریپت اجرا می کند و نتایج را با استفاده از کانال جاوا اسکریپت SnackBar پس می دهد.

اکنون که WebViewWidget از کانال‌های جاوا اسکریپت ما اطلاع دارد , یک مثال برای گسترش بیشتر برنامه اضافه می‌کنید. برای انجام این کار، یک PopupMenuItem اضافی به کلاس Menu اضافه کنید و عملکرد اضافی را اضافه کنید.

با افزودن مقدار javascriptChannel enumeration، با گزینه منوی اضافی، _MenuOptions را به روز کنید و پیاده سازی را به صورت زیر به کلاس Menu اضافه کنید:

lib/src/menu.dart

enum _MenuOptions {
  navigationDelegate,
  userAgent,
  javascriptChannel,                                      // Add this option
}

class Menu extends StatefulWidget {
  const Menu({required this.controller, super.key});

  final WebViewController controller;

  @override
  State<Menu> createState() => _MenuState();
}

class _MenuState extends State<Menu> {
  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<_MenuOptions>(
      onSelected: (value) async {
        switch (value) {
          case _MenuOptions.navigationDelegate:
            await widget.controller
                .loadRequest(Uri.parse('https://youtube.com'));
          case _MenuOptions.userAgent:
            final userAgent = await widget.controller
                .runJavaScriptReturningResult('navigator.userAgent');
            if (!context.mounted) return;
            ScaffoldMessenger.of(context).showSnackBar(SnackBar(
              content: Text('$userAgent'),
            ));
          case _MenuOptions.javascriptChannel:            // Add from here
            await widget.controller.runJavaScript('''
var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
  if (req.status == 200) {
    let response = JSON.parse(req.responseText);
    SnackBar.postMessage("IP Address: " + response.ip);
  } else {
    SnackBar.postMessage("Error: " + req.status);
  }
}
req.send();''');                                          // To here.
        }
      },
      itemBuilder: (context) => [
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.navigationDelegate,
          child: Text('Navigate to YouTube'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.userAgent,
          child: Text('Show user-agent'),
        ),
        const PopupMenuItem<_MenuOptions>(                // Add from here
          value: _MenuOptions.javascriptChannel,
          child: Text('Lookup IP Address'),
        ),                                                // To here.
      ],
    );
  }
}

این جاوا اسکریپت زمانی اجرا می شود که کاربر گزینه منوی JavaScript Channel Example را انتخاب کند.

var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
  if (req.status == 200) {
    SnackBar.postMessage(req.responseText);
  } else {
    SnackBar.postMessage("Error: " + req.status);
  }
}
req.send();

این کد یک درخواست GET را به یک API آدرس IP عمومی ارسال می کند و آدرس IP دستگاه را برمی گرداند. این نتیجه در SnackBar با فراخوانی postMessage در JavascriptChannel SnackBar نشان داده می شود.

11. مدیریت کوکی ها

برنامه شما می‌تواند کوکی‌ها را در WebView با استفاده از کلاس CookieManager مدیریت کند. در این مرحله می‌خواهید فهرستی از کوکی‌ها را نشان دهید، فهرست کوکی‌ها را پاک کنید، کوکی‌ها را حذف کنید و کوکی‌های جدیدی تنظیم کنید. برای هر یک از موارد استفاده از کوکی، ورودی های _MenuOptions را به شرح زیر اضافه کنید:

lib/src/menu.dart

enum _MenuOptions {
  navigationDelegate,
  userAgent,
  javascriptChannel,
  // Add from here ...
  listCookies,
  clearCookies,
  addCookie,
  setCookie,
  removeCookie,
  // ... to here.
}

بقیه تغییرات در این مرحله بر روی کلاس Menu متمرکز شده است، از جمله تبدیل کلاس Menu از حالت بدون حالت به حالت حالت. این تغییر مهم است زیرا Menu باید دارای CookieManager باشد و حالت تغییرپذیر در ویجت‌های بدون حالت ترکیب بدی است.

CookieManager را به صورت زیر به کلاس State حاصل اضافه کنید:

lib/src/menu.dart

class Menu extends StatefulWidget {
  const Menu({required this.controller, super.key});

  final WebViewController controller;

  @override
  State<Menu> createState() => _MenuState();
}

class _MenuState extends State<Menu> {
  final cookieManager = WebViewCookieManager();       // Add this line

  @override
  Widget build(BuildContext context) {
  // ...

کلاس _MenuState حاوی کدی است که قبلاً در کلاس Menu اضافه شده است، همراه با CookieManager که به تازگی اضافه شده است. در سری بعدی بخش ها، توابع کمکی را به _MenuState اضافه می کنید که به نوبه خود توسط آیتم های منو که هنوز اضافه نشده اند فراخوانی می شوند.

لیستی از تمام کوکی ها را دریافت کنید

شما می خواهید از جاوا اسکریپت برای دریافت لیستی از تمام کوکی ها استفاده کنید. برای رسیدن به این هدف، یک متد کمکی به انتهای کلاس _MenuState به نام _onListCookies اضافه کنید. با استفاده از روش runJavaScriptReturningResult ، روش کمکی شما document.cookie را در زمینه جاوا اسکریپت اجرا می‌کند و فهرستی از همه کوکی‌ها را برمی‌گرداند.

موارد زیر را به کلاس _MenuState اضافه کنید:

lib/src/menu.dart

Future<void> _onListCookies(WebViewController controller) async {
  final String cookies = await controller
      .runJavaScriptReturningResult('document.cookie') as String;
  if (!mounted) return;
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: Text(cookies.isNotEmpty ? cookies : 'There are no cookies.'),
    ),
  );
}

تمام کوکی ها را پاک کنید

برای پاک کردن تمام کوکی‌ها در WebView، از روش clearCookies کلاس CookieManager استفاده کنید. این روش Future<bool> را برمی‌گرداند که اگر CookieManager کوکی‌ها را پاک کند، مقدار true و اگر کوکی‌هایی برای پاک کردن وجود نداشته باشد false .

موارد زیر را به کلاس _MenuState اضافه کنید:

lib/src/menu.dart

Future<void> _onClearCookies() async {
  final hadCookies = await cookieManager.clearCookies();
  String message = 'There were cookies. Now, they are gone!';
  if (!hadCookies) {
    message = 'There were no cookies to clear.';
  }
  if (!mounted) return;
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: Text(message),
    ),
  );
}

افزودن کوکی را می توان با فراخوانی جاوا اسکریپت انجام داد. API مورد استفاده برای افزودن یک کوکی به یک سند جاوا اسکریپت به طور عمیق در MDN مستند شده است.

موارد زیر را به کلاس _MenuState اضافه کنید:

lib/src/menu.dart

Future<void> _onAddCookie(WebViewController controller) async {
  await controller.runJavaScript('''var date = new Date();
  date.setTime(date.getTime()+(30*24*60*60*1000));
  document.cookie = "FirstName=John; expires=" + date.toGMTString();''');
  if (!mounted) return;
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(
      content: Text('Custom cookie added.'),
    ),
  );
}

همچنین می‌توان کوکی‌ها را با استفاده از CookieManager به صورت زیر تنظیم کرد.

موارد زیر را به کلاس _MenuState اضافه کنید:

lib/src/menu.dart

Future<void> _onSetCookie(WebViewController controller) async {
  await cookieManager.setCookie(
    const WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev'),
  );
  if (!mounted) return;
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(
      content: Text('Custom cookie is set.'),
    ),
  );
}

حذف یک کوکی شامل افزودن یک کوکی با تاریخ انقضا در گذشته است.

موارد زیر را به کلاس _MenuState اضافه کنید:

lib/src/menu.dart

Future<void> _onRemoveCookie(WebViewController controller) async {
  await controller.runJavaScript(
      'document.cookie="FirstName=John; expires=Thu, 01 Jan 1970 00:00:00 UTC" ');
  if (!mounted) return;
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(
      content: Text('Custom cookie removed.'),
    ),
  );
}

افزودن موارد منوی CookieManager

تنها چیزی که باقی می‌ماند این است که گزینه‌های منو را اضافه کنید و آنها را به روش‌های کمکی که اخیراً اضافه کرده‌اید متصل کنید. کلاس _MenuState را به صورت زیر به روز کنید:

lib/src/menu.dart

class _MenuState extends State<Menu> {
  final cookieManager = WebViewCookieManager();

  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<_MenuOptions>(
      onSelected: (value) async {
        switch (value) {
          case _MenuOptions.navigationDelegate:
            await widget.controller
                .loadRequest(Uri.parse('https://youtube.com'));
          case _MenuOptions.userAgent:
            final userAgent = await widget.controller
                .runJavaScriptReturningResult('navigator.userAgent');
            if (!context.mounted) return;
            ScaffoldMessenger.of(context).showSnackBar(SnackBar(
              content: Text('$userAgent'),
            ));
          case _MenuOptions.javascriptChannel:
            await widget.controller.runJavaScript('''
var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
  if (req.status == 200) {
    let response = JSON.parse(req.responseText);
    SnackBar.postMessage("IP Address: " + response.ip);
  } else {
    SnackBar.postMessage("Error: " + req.status);
  }
}
req.send();''');
          case _MenuOptions.clearCookies:                        // Add from here
            await _onClearCookies();
          case _MenuOptions.listCookies:
            await _onListCookies(widget.controller);
          case _MenuOptions.addCookie:
            await _onAddCookie(widget.controller);
          case _MenuOptions.setCookie:
            await _onSetCookie(widget.controller);
          case _MenuOptions.removeCookie:
            await _onRemoveCookie(widget.controller);            // To here.
        }
      },
      itemBuilder: (context) => [
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.navigationDelegate,
          child: Text('Navigate to YouTube'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.userAgent,
          child: Text('Show user-agent'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.javascriptChannel,
          child: Text('Lookup IP Address'),
        ),
        const PopupMenuItem<_MenuOptions>(                       // Add from here
          value: _MenuOptions.clearCookies,
          child: Text('Clear cookies'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.listCookies,
          child: Text('List cookies'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.addCookie,
          child: Text('Add cookie'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.setCookie,
          child: Text('Set cookie'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.removeCookie,
          child: Text('Remove cookie'),
        ),                                                       // To here.
      ],
    );
  }

تمرین CookieManager

برای استفاده از تمام قابلیت هایی که به تازگی به برنامه اضافه کرده اید، مراحل زیر را امتحان کنید:

  1. فهرست کوکی ها را انتخاب کنید. باید کوکی های Google Analytics تنظیم شده توسط flutter.dev را فهرست کند.
  2. پاک کردن کوکی ها را انتخاب کنید. باید گزارش دهد که کوکی ها واقعاً پاک شده اند.
  3. دوباره پاک کردن کوکی ها را انتخاب کنید. باید گزارش دهد که هیچ کوکی برای پاک کردن موجود نیست.
  4. فهرست کوکی ها را انتخاب کنید. باید گزارش دهد که هیچ کوکی وجود ندارد.
  5. افزودن کوکی را انتخاب کنید. باید کوکی را به عنوان اضافه گزارش کند.
  6. تنظیم کوکی را انتخاب کنید. باید کوکی را به عنوان تنظیم گزارش کند.
  7. لیست کوکی ها را انتخاب کنید و سپس به عنوان شکوفایی نهایی، حذف کوکی را انتخاب کنید.

تصویری از یک شبیه‌ساز اندرویدی که یک برنامه Flutter را با یک نمای وب تعبیه‌شده اجرا می‌کند که صفحه اصلی Flutter.dev را با فهرستی از گزینه‌های منو نشان می‌دهد که شامل پیمایش به YouTube، نمایش عامل کاربر، و تعامل با شیشه کوکی مرورگر می‌شود.

تصویری از یک شبیه‌ساز Android که یک برنامه Flutter را با یک نمای وب تعبیه‌شده اجرا می‌کند که صفحه اصلی Flutter.dev را با یک پنجره بازشو نشان می‌دهد که کوکی‌های تنظیم شده در مرورگر را نشان می‌دهد.

تصویری از یک شبیه‌ساز اندرویدی که یک برنامه Flutter را با نمای وب تعبیه‌شده اجرا می‌کند که صفحه اصلی Flutter.dev را با یک پنجره بازشو نشان می‌دهد که روی آن نوشته شده است: «کوکی‌هایی وجود داشت». حالا آنها رفته اند!

تصویری از یک شبیه‌ساز Android که یک برنامه Flutter را با نمای وب تعبیه‌شده اجرا می‌کند که صفحه اصلی Flutter.dev را با یک پنجره بازشو نشان می‌دهد که «کوکی سفارشی اضافه شد» را می‌خواند.

12. دارایی های Flutter، فایل ها و رشته های HTML را در WebView بارگیری کنید

برنامه شما می تواند فایل های HTML را با استفاده از روش های مختلف بارگیری کند و آنها را در WebView نمایش دهد. در این مرحله شما یک دارایی Flutter مشخص شده در فایل pubspec.yaml را بارگیری می کنید، یک فایل واقع در مسیر مشخص شده را بارگیری می کنید و یک صفحه را با استفاده از یک رشته HTML بارگذاری می کنید.

اگر می خواهید فایلی را که در یک مسیر مشخص قرار دارد بارگیری کنید، باید path_provider به pubspec.yaml اضافه کنید. این یک پلاگین Flutter برای یافتن مکان های رایج در سیستم فایل است.

در خط فرمان، دستور زیر را اجرا کنید:

$ flutter pub add path_provider

برای بارگیری دارایی باید مسیر دارایی را در pubspec.yaml مشخص کنیم. در pubspec.yaml خطوط زیر را اضافه کنید:

pubspec.yaml

# The following section is specific to Flutter packages.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true
  # Add from here
  assets:
    - assets/www/index.html
    - assets/www/styles/style.css
  # ... to here.

برای افزودن دارایی ها به پروژه خود، مراحل زیر را انجام دهید:

  1. یک فهرست جدید با نام assets در پوشه اصلی پروژه خود ایجاد کنید.
  2. یک دایرکتوری جدید با نام www در پوشه assets ایجاد کنید.
  3. یک دایرکتوری جدید با styles نام در پوشه www ایجاد کنید.
  4. یک فایل جدید با نام index.html در پوشه www ایجاد کنید.
  5. یک فایل جدید با نام style.css در پوشه styles ایجاد کنید.

کد زیر را در فایل index.html کپی و پیست کنید:

دارایی/www/index.html

<!DOCTYPE html>
<!-- Copyright 2013 The Flutter Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<html lang="en">
<head>
    <title>Load file or HTML string example</title>
    <link rel="stylesheet" href="styles/style.css" />
</head>
<body>

<h1>Local demo page</h1>
<p>
    This is an example page used to demonstrate how to load a local file or HTML
    string using the <a href="https://pub.dev/packages/webview_flutter">Flutter
    webview</a> plugin.
</p>

</body>
</html>

برای style.css از چند خط زیر برای تنظیم سبک هدر HTML استفاده کنید:

assets/www/styles/style.css

h1 {
  color: blue;
}

اکنون که دارایی‌ها تنظیم شده و آماده استفاده هستند، می‌توانید روش‌هایی را که برای بارگیری و نمایش دارایی‌های Flutter، فایل‌ها یا رشته‌های HTML مورد نیاز است، پیاده‌سازی کنید.

بارگذاری دارایی فلاتر

برای بارگذاری دارایی ای که به تازگی ایجاد کرده اید، تنها کاری که باید انجام دهید این است که با استفاده از WebViewController ، متد loadFlutterAsset فراخوانی کنید و به عنوان پارامتر مسیر دارایی را ارائه دهید. روش زیر را در انتهای کد خود اضافه کنید:

lib/src/menu.dart

Future<void> _onLoadFlutterAssetExample(
    WebViewController controller, BuildContext context) async {
  await controller.loadFlutterAsset('assets/www/index.html');
}

فایل محلی را بارگیری کنید

برای بارگذاری یک فایل در دستگاه خود، می توانید روشی را اضافه کنید که از روش loadFile استفاده می کند، دوباره با استفاده از WebViewController که String حاوی مسیر فایل را می گیرد.

ابتدا باید یک فایل حاوی کد HTML بسازید. شما به سادگی می توانید این کار را با افزودن کد HTML به عنوان یک رشته در بالای کد خود در فایل menu.dart درست در زیر ورودی ها انجام دهید.

lib/src/menu.dart

import 'dart:io';                                   // Add this line,
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';  // And this one.
import 'package:webview_flutter/webview_flutter.dart';

// Add from here ...
const String kExamplePage = '''
<!DOCTYPE html>
<html lang="en">
<head>
<title>Load file or HTML string example</title>
</head>
<body>

<h1>Local demo page</h1>
<p>
 This is an example page used to demonstrate how to load a local file or HTML
 string using the <a href="https://pub.dev/packages/webview_flutter">Flutter
 webview</a> plugin.
</p>

</body>
</html>
''';
// ... to here.

برای ایجاد یک File و نوشتن رشته HTML در فایل، دو روش اضافه می کنید. _onLoadLocalFileExample با ارائه مسیری به عنوان رشته ای که توسط متد _prepareLocalFile() برگردانده می شود، فایل را بارگیری می کند. روش های زیر را به کد خود اضافه کنید:

lib/src/menu.dart

Future<void> _onLoadLocalFileExample(
    WebViewController controller, BuildContext context) async {
  final String pathToIndex = await _prepareLocalFile();

  await controller.loadFile(pathToIndex);
}

static Future<String> _prepareLocalFile() async {
  final String tmpDir = (await getTemporaryDirectory()).path;
  final File indexFile = File('$tmpDir/www/index.html');

  await Directory('$tmpDir/www').create(recursive: true);
  await indexFile.writeAsString(kExamplePage);

  return indexFile.path;
}

رشته HTML را بارگیری کنید

نمایش یک صفحه با ارائه یک رشته HTML بسیار ساده است. WebViewController روشی دارد که می توانید از آن به نام loadHtmlString استفاده کنید که در آن می توانید رشته HTML را به عنوان آرگومان ارائه دهید. سپس WebView صفحه HTML ارائه شده را نمایش می دهد. روش زیر را به کد خود اضافه کنید:

lib/src/menu.dart

Future<void> _onLoadFlutterAssetExample(
    WebViewController controller, BuildContext context) async {
  await controller.loadFlutterAsset('assets/www/index.html');
}

Future<void> _onLoadLocalFileExample(
    WebViewController controller, BuildContext context) async {
  final String pathToIndex = await _prepareLocalFile();

  await controller.loadFile(pathToIndex);
}

static Future<String> _prepareLocalFile() async {
  final String tmpDir = (await getTemporaryDirectory()).path;
  final File indexFile = File('$tmpDir/www/index.html');

  await Directory('$tmpDir/www').create(recursive: true);
  await indexFile.writeAsString(kExamplePage);

  return indexFile.path;
}

// Add here ...
Future<void> _onLoadHtmlStringExample(
    WebViewController controller, BuildContext context) async {
  await controller.loadHtmlString(kExamplePage);
}
// ... to here.

موارد منو را اضافه کنید

اکنون که دارایی ها تنظیم شده و آماده استفاده هستند و روش ها با تمام قابلیت ها ساخته شده اند، می توان منو را به روز کرد. ورودی های زیر را به فهرست _MenuOptions اضافه کنید:

lib/src/menu.dart

enum _MenuOptions {
  navigationDelegate,
  userAgent,
  javascriptChannel,
  listCookies,
  clearCookies,
  addCookie,
  setCookie,
  removeCookie,
  // Add from here ...
  loadFlutterAsset,
  loadLocalFile,
  loadHtmlString,
  // ... to here.
}

اکنون که enum به‌روزرسانی شده است، می‌توانید گزینه‌های منو را اضافه کنید و آنها را به روش‌های کمکی که اخیراً اضافه کرده‌اید متصل کنید. کلاس _MenuState را به صورت زیر به روز کنید:

lib/src/menu.dart

class _MenuState extends State<Menu> {
  final cookieManager = WebViewCookieManager();

  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<_MenuOptions>(
      onSelected: (value) async {
        switch (value) {
          case _MenuOptions.navigationDelegate:
            await widget.controller
                .loadRequest(Uri.parse('https://youtube.com'));
          case _MenuOptions.userAgent:
            final userAgent = await widget.controller
                .runJavaScriptReturningResult('navigator.userAgent');
            if (!context.mounted) return;
            ScaffoldMessenger.of(context).showSnackBar(SnackBar(
              content: Text('$userAgent'),
            ));
          case _MenuOptions.javascriptChannel:
            await widget.controller.runJavaScript('''
var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
  if (req.status == 200) {
    let response = JSON.parse(req.responseText);
    SnackBar.postMessage("IP Address: " + response.ip);
  } else {
    SnackBar.postMessage("Error: " + req.status);
  }
}
req.send();''');
          case _MenuOptions.clearCookies:
            await _onClearCookies();
          case _MenuOptions.listCookies:
            await _onListCookies(widget.controller);
          case _MenuOptions.addCookie:
            await _onAddCookie(widget.controller);
          case _MenuOptions.setCookie:
            await _onSetCookie(widget.controller);
          case _MenuOptions.removeCookie:
            await _onRemoveCookie(widget.controller);
          case _MenuOptions.loadFlutterAsset:             // Add from here
            if (!mounted) return;
            await _onLoadFlutterAssetExample(widget.controller, context);
          case _MenuOptions.loadLocalFile:
            if (!mounted) return;
            await _onLoadLocalFileExample(widget.controller, context);
          case _MenuOptions.loadHtmlString:
            if (!mounted) return;
            await _onLoadHtmlStringExample(widget.controller, context);
                                                          // To here.
        }
      },
      itemBuilder: (context) => [
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.navigationDelegate,
          child: Text('Navigate to YouTube'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.userAgent,
          child: Text('Show user-agent'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.javascriptChannel,
          child: Text('Lookup IP Address'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.clearCookies,
          child: Text('Clear cookies'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.listCookies,
          child: Text('List cookies'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.addCookie,
          child: Text('Add cookie'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.setCookie,
          child: Text('Set cookie'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.removeCookie,
          child: Text('Remove cookie'),
        ),
        const PopupMenuItem<_MenuOptions>(                // Add from here
          value: _MenuOptions.loadFlutterAsset,
          child: Text('Load Flutter Asset'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.loadHtmlString,
          child: Text('Load HTML string'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.loadLocalFile,
          child: Text('Load local file'),
        ),                                                // To here.
      ],
    );
  }

تست دارایی ها، فایل ها و رشته های HTML

برای آزمایش اینکه آیا کدی که به تازگی پیاده‌سازی کرده‌اید کار می‌کند، می‌توانید کد را روی دستگاه خود اجرا کنید و روی یکی از آیتم‌های منو که به تازگی اضافه شده است کلیک کنید. توجه کنید که چگونه _onLoadFlutterAssetExample از style.css که اضافه کردیم برای تغییر هدر فایل HTML به رنگ آبی استفاده می کند.

تصویری از یک شبیه‌ساز اندرویدی که یک برنامه Flutter را با نمای وب تعبیه‌شده اجرا می‌کند که صفحه‌ای با برچسب «صفحه نمایشی محلی» با عنوان آبی نشان می‌دهد.

تصویری از یک شبیه‌ساز اندرویدی که یک برنامه Flutter را با نمای وب تعبیه‌شده اجرا می‌کند که صفحه‌ای با برچسب «صفحه نمایشی محلی» با عنوان سیاه و سفید نشان می‌دهد.

13. همه چیز تمام شد!

تبریک میگم!!! شما آزمایشگاه کد را تکمیل کرده اید. شما می توانید کد تکمیل شده برای این کد لبه را در مخزن کد لبه پیدا کنید.

برای کسب اطلاعات بیشتر، سایر کدهای Flutter را امتحان کنید.