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

۱. مقدمه

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

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

آنچه خواهید ساخت

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

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

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

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

آنچه یاد خواهید گرفت

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

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

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

۲. محیط توسعه فلاتر خود را تنظیم کنید

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

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

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

۳. شروع کار

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

روش‌های مختلفی برای ایجاد یک پروژه جدید Flutter وجود دارد که هم اندروید استودیو و هم ویژوال استودیو کد ابزارهایی برای این کار ارائه می‌دهند. یا می‌توانید رویه‌های مرتبط را برای ایجاد یک پروژه دنبال کنید، یا دستورات زیر را در یک ترمینال خط فرمان مفید اجرا کنید.

$ 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 {
    //...

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

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

در این مرحله شما یک WebView به برنامه خود اضافه خواهید کرد. WebViewها، نماهای داخلی میزبانی شده هستند و شما به عنوان یک توسعه‌دهنده برنامه می‌توانید نحوه میزبانی این نماهای داخلی را در برنامه خود انتخاب کنید. در اندروید، می‌توانید بین Virtual Displays، که پیش‌فرض اندروید است، و Hybrid composition یکی را انتخاب کنید. با این حال، iOS همیشه از Hybrid composition استفاده می‌کند.

برای بحث عمیق‌تر در مورد تفاوت‌های بین نمایشگرهای مجازی و ترکیب ترکیبی، مستندات مربوط به میزبانی نماهای بومی اندروید و iOS در برنامه Flutter خود با استفاده از Platform 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 یا اندروید، یک WebView را به عنوان یک پنجره مرورگر با حاشیه کامل (full bleed) در دستگاه شما نشان می‌دهد، به این معنی که مرورگر در دستگاه شما به صورت تمام صفحه و بدون هیچ گونه حاشیه یا حاشیه‌ای نمایش داده می‌شود. هنگام اسکرول کردن، متوجه قسمت‌هایی از صفحه خواهید شد که ممکن است کمی عجیب به نظر برسند. دلیل این امر غیرفعال بودن جاوا اسکریپت است و رندر صحیح flutter.dev به جاوا اسکریپت نیاز دارد.

اجرای برنامه

برنامه Flutter را در iOS یا اندروید اجرا کنید تا یک Webview را ببینید که وب‌سایت flutter.dev را نمایش می‌دهد. همچنین می‌توانید برنامه را در یک شبیه‌ساز اندروید یا یک شبیه‌ساز iOS اجرا کنید. می‌توانید آدرس اولیه WebView را مثلاً با وب‌سایت خودتان جایگزین کنید.

$ flutter run

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

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

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

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

ویجت 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 همپوشانی می‌دهد. از آنجایی که این شامل وضعیت برنامه است که با گذشت زمان تغییر می‌کند، شما این وضعیت را در یک کلاس 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 مشاهده خواهید کرد.

۶. کار با WebViewController

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

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

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 را با کنترل‌های صفحه قبل، صفحه بعد و بارگذاری مجدد صفحه نشان می‌دهد.

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

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

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

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

در این مرحله، شما یک فراخوانی NavigationDelegate برای مسدود کردن ناوبری به YouTube.com ثبت خواهید کرد. توجه داشته باشید که این پیاده‌سازی ساده، محتوای درون‌خطی یوتیوب را نیز مسدود می‌کند، که در صفحات مختلف مستندات 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,
          ),
      ],
    );
  }
}

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

۸. افزودن دکمه منو به 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'),
        ),
      ],
    );
  }
}

وقتی کاربر گزینه‌ی «رفتن به یوتیوب» را در منوی «انتخاب» انتخاب می‌کند، متد loadRequest از WebViewController اجرا می‌شود. این ناوبری توسط تابع فراخوانی navigationDelegate که در مرحله‌ی قبل ایجاد کردید، مسدود خواهد شد.

برای افزودن منو به صفحه 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),
    );
  }
}

برنامه خود را اجرا کنید و روی گزینه منو «به یوتیوب بروید» ضربه بزنید. باید با یک SnackBar مواجه شوید که به شما اطلاع می‌دهد که کنترل‌کننده ناوبری، پیمایش به یوتیوب را مسدود کرده است.

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

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

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

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.
      ],
    );
  }
}

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

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

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

۱۰. کار با کانال‌های جاوا اسکریپت

کانال‌های جاوااسکریپت به برنامه شما این امکان را می‌دهند که کنترل‌کننده‌های فراخوانی را در زمینه جاوااسکریپت 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 ، گزینه منوی اضافی _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.
      ],
    );
  }
}

این جاوا اسکریپت زمانی اجرا می‌شود که کاربر گزینه‌ی منوی «مثال کانال جاوا اسکریپت» را انتخاب کند.

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 دستگاه را برمی‌گرداند. این نتیجه با فراخوانی postMessage در SnackBar JavascriptChannel در یک SnackBar نمایش داده می‌شود.

۱۱. مدیریت کوکی‌ها

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

lib/src/menu.dart

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

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

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. گزینه «لیست کوکی‌ها» را انتخاب کنید. در این قسمت باید کوکی‌های گوگل آنالیتیکس تنظیم‌شده توسط flutter.dev فهرست شوند.
  2. پاک کردن کوکی‌ها را انتخاب کنید. باید گزارش دهد که کوکی‌ها واقعاً پاک شده‌اند.
  3. دوباره پاک کردن کوکی‌ها را انتخاب کنید. باید گزارش دهد که هیچ کوکی برای پاک کردن در دسترس نیست.
  4. گزینه «لیست کوکی‌ها» را انتخاب کنید. باید گزارش دهد که هیچ کوکی وجود ندارد.
  5. گزینه «افزودن کوکی» را انتخاب کنید. باید کوکی به عنوان «افزوده شده» گزارش شود.
  6. گزینه «تنظیم کوکی» را انتخاب کنید. باید کوکی را به عنوان «تنظیم‌شده» گزارش دهد.
  7. گزینه «لیست کوکی‌ها» را انتخاب کنید و سپس به عنوان آخرین اقدام، گزینه «حذف کوکی» را انتخاب کنید.

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

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

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

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

۱۲. بارگذاری فایل‌های 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.

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

  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>

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

فایل‌ها/www/styles/style.css

h1 {
  color: blue;
}

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

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

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

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 درست زیر importها انجام دهید.

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.

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

اکنون که دارایی‌ها تنظیم و آماده استفاده هستند و متدها با تمام قابلیت‌ها ساخته شده‌اند، می‌توان منو را به‌روزرسانی کرد. ورودی‌های زیر را به enum _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 را با یک نمای وب جاسازی‌شده اجرا می‌کند و صفحه‌ای با برچسب «صفحه آزمایشی محلی» را با عنوان مشکی نشان می‌دهد

۱۳. همه چیز تمام شد!

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

برای کسب اطلاعات بیشتر، سایر آزمایشگاه‌های کد فلاتر را امتحان کنید.