একটি ফ্লাটার প্লাগইনে FFI ব্যবহার করা

1. ভূমিকা

ডার্টের এফএফআই (বিদেশী ফাংশন ইন্টারফেস) ফ্লটার অ্যাপগুলিকে বিদ্যমান নেটিভ লাইব্রেরি ব্যবহার করতে দেয় যা একটি C API প্রকাশ করে । ডার্ট অ্যান্ড্রয়েড, আইওএস, উইন্ডোজ, ম্যাকওএস এবং লিনাক্সে এফএফআই সমর্থন করে। ওয়েবের জন্য, ডার্ট জাভাস্ক্রিপ্ট ইন্টারপ সমর্থন করে, কিন্তু সেই বিষয় এই কোডল্যাবে কভার করা হয় না।

আপনি কি নির্মাণ করবেন

এই কোডল্যাবে, আপনি একটি মোবাইল এবং ডেস্কটপ প্লাগইন তৈরি করেন যা একটি সি লাইব্রেরি ব্যবহার করে। এই API এর সাথে, আপনি একটি সহজ উদাহরণ অ্যাপ লিখবেন যা প্লাগইন ব্যবহার করে। আপনার প্লাগইন এবং অ্যাপ হবে:

Duktape REPL একটি macOS অ্যাপ্লিকেশন হিসাবে চলছে

আপনি কি শিখবেন

এই কোডল্যাবে আপনি ডেস্কটপ এবং মোবাইল উভয় প্ল্যাটফর্মে একটি FFI-ভিত্তিক ফ্লাটার প্লাগইন তৈরি করার জন্য প্রয়োজনীয় ব্যবহারিক জ্ঞান শিখবেন, যার মধ্যে রয়েছে:

  • একটি ডার্ট এফএফআই ভিত্তিক ফ্লাটার প্লাগইন টেমপ্লেট তৈরি করা হচ্ছে
  • একটি C লাইব্রেরির জন্য বাইন্ডিং কোড তৈরি করতে ffigen প্যাকেজ ব্যবহার করে
  • অ্যান্ড্রয়েড, উইন্ডোজ এবং লিনাক্সের জন্য একটি ফ্লটার এফএফআই প্লাগইন তৈরি করতে CMake ব্যবহার করে
  • iOS এবং macOS-এর জন্য একটি Flutter FFI প্লাগইন তৈরি করতে CocoaPods ব্যবহার করা

আপনি কি প্রয়োজন হবে

  • অ্যান্ড্রয়েড ডেভেলপমেন্টের জন্য অ্যান্ড্রয়েড স্টুডিও 4.1 বা তার পরে
  • iOS এবং macOS ডেভেলপমেন্টের জন্য Xcode 13 বা তার পরে
  • উইন্ডোজ ডেস্কটপ ডেভেলপমেন্টের জন্য "C++ এর সাথে ডেস্কটপ ডেভেলপমেন্ট" ওয়ার্কলোড সহ ভিজ্যুয়াল স্টুডিও 2022 বা ভিজ্যুয়াল স্টুডিও বিল্ড টুল 2022
  • ফ্লটার SDK
  • আপনি যে প্ল্যাটফর্মগুলিতে বিকাশ করবেন তার জন্য যে কোনও প্রয়োজনীয় বিল্ড সরঞ্জাম (উদাহরণস্বরূপ, CMake, CocoaPods, এবং তাই)।
  • আপনি যে প্ল্যাটফর্মগুলিতে বিকাশ করবেন তার জন্য LLVM । LLVM কম্পাইলার টুল স্যুটটি ডার্টে উন্মুক্ত এফএফআই বাইন্ডিং তৈরি করতে সি হেডার ফাইল পার্স করতে ffigen ব্যবহার করে।
  • একটি কোড সম্পাদক, যেমন ভিজ্যুয়াল স্টুডিও কোড

2. শুরু করা

ffigen টুলিং হল ফ্লটারের সাম্প্রতিক সংযোজন। আপনি নিশ্চিত করতে পারেন যে আপনার ফ্লাটার ইনস্টলেশনটি নিম্নলিখিত কমান্ডটি চালিয়ে বর্তমান স্থিতিশীল রিলিজ চালাচ্ছে।

$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.3.9, on macOS 13.1 22C65 darwin-arm, locale en)
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 14.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.2)
[✓] IntelliJ IDEA Community Edition (version 2022.2.2)
[✓] VS Code (version 1.74.0)
[✓] Connected device (2 available)
[✓] HTTP Host Availability

• No issues found!

নিশ্চিত করুন যে flutter doctor আউটপুট বলে যে আপনি স্থিতিশীল চ্যানেলে আছেন এবং সাম্প্রতিক স্থিতিশীল ফ্লাটার রিলিজ উপলব্ধ নেই। আপনি যদি স্থিতিশীল না থাকেন, বা আরও সাম্প্রতিক রিলিজ পাওয়া যায়, তাহলে আপনার ফ্লাটার টুলিংকে গতিতে আনতে নিম্নলিখিত দুটি কমান্ড চালান।

$ flutter channel stable
$ flutter upgrade

আপনি এই কোডল্যাবে এই ডিভাইসগুলির যেকোনো একটি ব্যবহার করে কোডটি চালাতে পারেন:

  • আপনার ডেভেলপমেন্ট কম্পিউটার (আপনার প্লাগইন এবং উদাহরণ অ্যাপের ডেস্কটপ বিল্ডের জন্য)
  • আপনার কম্পিউটারের সাথে সংযুক্ত এবং বিকাশকারী মোডে সেট করা একটি শারীরিক Android বা iOS ডিভাইস৷
  • আইওএস সিমুলেটর (এক্সকোড সরঞ্জামগুলি ইনস্টল করা প্রয়োজন)
  • অ্যান্ড্রয়েড এমুলেটর (অ্যান্ড্রয়েড স্টুডিওতে সেটআপ প্রয়োজন)

3. প্লাগইন টেমপ্লেট তৈরি করুন

Flutter প্লাগইন বিকাশের সাথে শুরু করা

প্লাগইনগুলির জন্য টেমপ্লেট সহ ফ্লাটার জাহাজ যা শুরু করা সহজ করে। আপনি যখন প্লাগইন টেমপ্লেট তৈরি করেন, আপনি কোন ভাষা ব্যবহার করতে চান তা নির্দিষ্ট করতে পারেন।

প্লাগইন টেমপ্লেট ব্যবহার করে আপনার প্রকল্প তৈরি করতে আপনার কাজের ডিরেক্টরিতে নিম্নলিখিত কমান্ডটি চালান:

$ flutter create --template=plugin_ffi \
  --platforms=android,ios,linux,macos,windows ffigen_app

--platforms প্যারামিটার নির্দিষ্ট করে যে আপনার প্লাগইন কোন প্ল্যাটফর্ম সমর্থন করবে।

আপনি tree কমান্ড বা আপনার অপারেটিং সিস্টেমের ফাইল এক্সপ্লোরার ব্যবহার করে উত্পন্ন প্রকল্পের বিন্যাস পরিদর্শন করতে পারেন।

$ tree -L 2 ffigen_app
ffigen_app
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android
│   ├── build.gradle
│   ├── ffigen_app_android.iml
│   ├── local.properties
│   ├── settings.gradle
│   └── src
├── example
│   ├── README.md
│   ├── analysis_options.yaml
│   ├── android
│   ├── ffigen_app_example.iml
│   ├── ios
│   ├── lib
│   ├── linux
│   ├── macos
│   ├── pubspec.lock
│   ├── pubspec.yaml
│   └── windows
├── ffigen.yaml
├── ffigen_app.iml
├── ios
│   ├── Classes
│   └── ffigen_app.podspec
├── lib
│   ├── ffigen_app.dart
│   └── ffigen_app_bindings_generated.dart
├── linux
│   └── CMakeLists.txt
├── macos
│   ├── Classes
│   └── ffigen_app.podspec
├── pubspec.lock
├── pubspec.yaml
├── src
│   ├── CMakeLists.txt
│   ├── ffigen_app.c
│   └── ffigen_app.h
└── windows
    └── CMakeLists.txt

17 directories, 26 files

কী তৈরি করা হয়েছে এবং এটি কোথায় অবস্থিত তার অনুভূতি পেতে ডিরেক্টরি কাঠামোর দিকে তাকিয়ে একটি মুহূর্ত ব্যয় করা মূল্যবান। plugin_ffi টেমপ্লেটটি lib , android , ios , linux , macos , এবং windows নামে প্লাটফর্ম-নির্দিষ্ট ডিরেক্টরি এবং সবচেয়ে গুরুত্বপূর্ণভাবে, একটি example ডিরেক্টরির অধীনে প্লাগইনের জন্য ডার্ট কোড রাখে।

স্বাভাবিক ফ্লাটার ডেভেলপমেন্টে অভ্যস্ত একজন ডেভেলপারের জন্য, এই কাঠামোটি অদ্ভুত মনে হতে পারে, কারণ শীর্ষ স্তরে কোন এক্সিকিউটেবল সংজ্ঞায়িত নেই। একটি প্লাগইনকে অন্যান্য ফ্লাটার প্রকল্পে অন্তর্ভুক্ত করার জন্য বোঝানো হয়, তবে আপনার প্লাগইন কোড কাজ করছে কিনা তা নিশ্চিত করতে আপনি example ডিরেক্টরিতে কোডটি বের করবেন।

এটা শুরু করার সময়!

4. উদাহরণ তৈরি করুন এবং চালান

বিল্ড সিস্টেম এবং পূর্বশর্তগুলি সঠিকভাবে ইনস্টল করা হয়েছে এবং প্রতিটি সমর্থিত প্ল্যাটফর্মের জন্য কাজ করছে তা নিশ্চিত করতে, প্রতিটি লক্ষ্যের জন্য জেনারেট করা উদাহরণ অ্যাপ তৈরি করুন এবং চালান।

উইন্ডোজ

আপনি Windows এর একটি সমর্থিত সংস্করণ ব্যবহার করছেন তা নিশ্চিত করুন। এই কোডল্যাবটি উইন্ডোজ 10 এবং উইন্ডোজ 11 এ কাজ করার জন্য পরিচিত।

আপনি হয় আপনার কোড এডিটর থেকে বা কমান্ড লাইনে অ্যাপ্লিকেশনটি তৈরি করতে পারেন।

PS C:\Users\brett\Documents> cd .\ffigen_app\example\
PS C:\Users\brett\Documents\ffigen_app\example> flutter run -d windows
Launching lib\main.dart on Windows in debug mode...Building Windows application...
Syncing files to device Windows...                                 160ms

Flutter run key commands.
r Hot reload.
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).

 Running with sound null safety

An Observatory debugger and profiler on Windows is available at: http://127.0.0.1:53317/OiKWpyHXxHI=/
The Flutter DevTools debugger and profiler on Windows is available at:
http://127.0.0.1:9100?uri=http://127.0.0.1:53317/OiKWpyHXxHI=/

আপনি নিম্নলিখিত মত একটি চলমান অ্যাপ্লিকেশন উইন্ডো দেখতে হবে:

টেমপ্লেট জেনারেটেড এফএফআই অ্যাপ উইন্ডোজ অ্যাপ হিসেবে চলছে

লিনাক্স

নিশ্চিত করুন যে আপনি লিনাক্সের একটি সমর্থিত সংস্করণ ব্যবহার করছেন। এই কোডল্যাবটি Ubuntu 22.04.1 ব্যবহার করে।

একবার আপনি ধাপ 2 এ তালিকাভুক্ত সমস্ত পূর্বশর্ত ইনস্টল করার পরে, একটি টার্মিনালে নিম্নলিখিত কমান্ডগুলি চালান:

$ cd ffigen_app/example
$ flutter run -d linux
Launching lib/main.dart on Linux in debug mode...
Building Linux application...
Syncing files to device Linux...                                   504ms

Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).

💪 Running with sound null safety 💪

An Observatory debugger and profiler on Linux is available at: http://127.0.0.1:36653/Wgek1JGag48=/
The Flutter DevTools debugger and profiler on Linux is available at:
http://127.0.0.1:9103?uri=http://127.0.0.1:36653/Wgek1JGag48=/

আপনি নিম্নলিখিত মত একটি চলমান অ্যাপ্লিকেশন উইন্ডো দেখতে হবে:

টেমপ্লেট জেনারেটেড এফএফআই অ্যাপ লিনাক্স অ্যাপ্লিকেশন হিসেবে চলছে

অ্যান্ড্রয়েড

অ্যান্ড্রয়েডের জন্য আপনি সংকলনের জন্য উইন্ডোজ, ম্যাকওএস বা লিনাক্স ব্যবহার করতে পারেন। প্রথমে, নিশ্চিত করুন যে আপনার ডেভেলপমেন্ট কম্পিউটারের সাথে সংযুক্ত একটি Android ডিভাইস আছে বা আপনি একটি Android এমুলেটর (AVD) উদাহরণ চালাচ্ছেন। নিশ্চিত করুন যে Flutter নিম্নলিখিতগুলি চালিয়ে Android ডিভাইস বা এমুলেটরের সাথে সংযোগ করতে সক্ষম:

$ flutter devices
3 connected devices:

sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64  • Android 12 (API 32) (emulator)
macOS (desktop)             • macos         • darwin-arm64   • macOS 13.1 22C65 darwin-arm
Chrome (web)                • chrome        • web-javascript • Google Chrome 108.0.5359.98

টেমপ্লেট জেনারেটেড এফএফআই অ্যাপ একটি অ্যান্ড্রয়েড এমুলেটরে চলছে

macOS এবং iOS

macOS এবং iOS Flutter বিকাশের জন্য, আপনাকে অবশ্যই একটি macOS কম্পিউটার ব্যবহার করতে হবে।

ম্যাকওএস-এ উদাহরণ অ্যাপটি চালানোর সাথে শুরু করুন। Flutter যে ডিভাইসগুলি দেখে সেগুলি আবার নিশ্চিত করুন:

$ flutter devices
2 connected devices:

macOS (desktop) • macos  • darwin-arm64   • macOS 13.1 22C65 darwin-arm
Chrome (web)    • chrome • web-javascript • Google Chrome 108.0.5359.98

উত্পন্ন প্লাগইন প্রকল্প ব্যবহার করে উদাহরণ অ্যাপ্লিকেশন চালান:

$ cd ffigen_app/example
$ flutter run -d macos

আপনি নিম্নলিখিত মত একটি চলমান অ্যাপ্লিকেশন উইন্ডো দেখতে হবে:

টেমপ্লেট জেনারেটেড এফএফআই অ্যাপ লিনাক্স অ্যাপ্লিকেশন হিসেবে চলছে

iOS এর জন্য আপনি সিমুলেটর বা একটি বাস্তব হার্ডওয়্যার ডিভাইস ব্যবহার করতে পারেন। সিমুলেটর ব্যবহার করলে, প্রথমে সিমুলেটর চালু করুন। flutter devices কমান্ড এখন সিমুলেটরটিকে তার উপলব্ধ ডিভাইসগুলির মধ্যে একটি হিসাবে তালিকাভুক্ত করে।

$ flutter devices
3 connected devices:

iPhone SE (3rd generation) (mobile) • 1BCBE334-7EC4-433A-90FD-1BC14F3BA41F • ios            • com.apple.CoreSimulator.SimRuntime.iOS-16-1 (simulator)
macOS (desktop)                     • macos                                • darwin-arm64   • macOS 13.1 22C65 darwin-arm
Chrome (web)                        • chrome                               • web-javascript • Google Chrome 108.0.5359.98

সিমুলেটর চালু হয়ে গেলে রান করুন: flutter run

$ cd ffigen_app/example
$ flutter run -d iphone

টেমপ্লেট জেনারেট করা FFI অ্যাপ একটি iOS সিমুলেটরে চলছে

iOS সিমুলেটরটি macOS টার্গেটের উপর অগ্রাধিকার নেয়, তাই আপনি -d প্যারামিটার সহ একটি ডিভাইস নির্দিষ্ট করা এড়িয়ে যেতে পারেন।

অভিনন্দন, আপনি সফলভাবে পাঁচটি ভিন্ন অপারেটিং সিস্টেমে একটি অ্যাপ্লিকেশন তৈরি ও চালান করেছেন। পরবর্তীতে, নেটিভ প্লাগইন তৈরি করা এবং এফএফআই ব্যবহার করে ডার্ট থেকে এটির সাথে ইন্টারফেস করা।

5. Windows, Linux, এবং Android-এ Duktape ব্যবহার করা

আপনি এই কোডল্যাবে যে সি লাইব্রেরিটি ব্যবহার করবেন তা হল Duktape । Duktape হল একটি এমবেডযোগ্য জাভাস্ক্রিপ্ট ইঞ্জিন, যা বহনযোগ্যতা এবং একটি কমপ্যাক্ট পদচিহ্নের উপর ফোকাস সহ। এই ধাপে, আপনি Duktape লাইব্রেরি কম্পাইল করার জন্য প্লাগইনটি কনফিগার করবেন, এটিকে আপনার প্লাগইনের সাথে লিঙ্ক করবেন এবং তারপর ডার্টের FFI ব্যবহার করে এটি অ্যাক্সেস করবেন।

এই ধাপটি Windows, Linux, এবং Android-এ কাজ করার জন্য ইন্টিগ্রেশন কনফিগার করে। iOS এবং macOS ইন্টিগ্রেশনের জন্য অতিরিক্ত কনফিগারেশন প্রয়োজন (এই ধাপে বিস্তারিত যা আছে তার বাইরে) কম্পাইল করা লাইব্রেরীকে চূড়ান্ত ফ্লাটার এক্সিকিউটেবলে অন্তর্ভুক্ত করতে। পরবর্তী ধাপে অতিরিক্ত প্রয়োজনীয় কনফিগারেশন কভার করা হয়েছে।

ডুকটেপ পুনরুদ্ধার করা হচ্ছে

প্রথমে, duktape.org ওয়েবসাইট থেকে ডাউনলোড করে duktape সোর্স কোডের একটি অনুলিপি পান।

উইন্ডোজের জন্য আপনি Invoke-WebRequest সহ PowerShell ব্যবহার করতে পারেন:

PS> Invoke-WebRequest -Uri https://duktape.org/duktape-2.7.0.tar.xz -OutFile duktape-2.7.0.tar.xz

লিনাক্সের জন্য, wget একটি ভাল পছন্দ।

$ wget https://duktape.org/duktape-2.7.0.tar.xz
--2022-12-22 16:21:39--  https://duktape.org/duktape-2.7.0.tar.xz
Resolving duktape.org (duktape.org)... 104.198.14.52
Connecting to duktape.org (duktape.org)|104.198.14.52|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1026524 (1002K) [application/x-xz]
Saving to: ‘duktape-2.7.0.tar.xz'

duktape-2.7.0.tar.x 100%[===================>]   1002K  1.01MB/s    in 1.0s

2022-12-22 16:21:41 (1.01 MB/s) - ‘duktape-2.7.0.tar.xz' saved [1026524/1026524]

ফাইলটি একটি tar.xz সংরক্ষণাগার। উইন্ডোজে, একটি বিকল্প হল 7Zip টুলগুলি ডাউনলোড করা এবং এটি নিম্নরূপ ব্যবহার করা।

PS> 7z x .\duktape-2.7.0.tar.xz

7-Zip 22.01 (x64) : Copyright (c) 1999-2022 Igor Pavlov : 2022-07-15

Scanning the drive for archives:
1 file, 1026524 bytes (1003 KiB)

Extracting archive: .\duktape-2.7.0.tar.xz
--
Path = .\duktape-2.7.0.tar.xz
Type = xz
Physical Size = 1026524
Method = LZMA2:26 CRC64
Streams = 1
Blocks = 1

Everything is Ok

Size:       19087360
Compressed: 1026524

আপনাকে দুইবার 7z চালাতে হবে, প্রথমে xz কম্প্রেশন আনআর্কাইভ করতে, দ্বিতীয়বার টার আর্কাইভ প্রসারিত করতে।

PS> 7z x .\duktape-2.7.0.tar

7-Zip 22.01 (x64) : Copyright (c) 1999-2022 Igor Pavlov : 2022-07-15

Scanning the drive for archives:
1 file, 19087360 bytes (19 MiB)

Extracting archive: .\duktape-2.7.0.tar
--
Path = .\duktape-2.7.0.tar
Type = tar
Physical Size = 19087360
Headers Size = 543232
Code Page = UTF-8
Characteristics = GNU ASCII

Everything is Ok

Folders: 46
Files: 1004
Size:       18281564
Compressed: 19087360

আধুনিক লিনাক্স পরিবেশে, tar নিম্নরূপ এক ধাপে বিষয়বস্তু বের করে।

$ tar xvf duktape-2.7.0.tar.xz
x duktape-2.7.0/
x duktape-2.7.0/README.rst
x duktape-2.7.0/Makefile.sharedlibrary
x duktape-2.7.0/Makefile.coffee
x duktape-2.7.0/extras/
x duktape-2.7.0/extras/README.rst
x duktape-2.7.0/extras/module-node/
x duktape-2.7.0/extras/module-node/README.rst
x duktape-2.7.0/extras/module-node/duk_module_node.h
x duktape-2.7.0/extras/module-node/Makefile
[... and many more files]

LLVM ইনস্টল করা হচ্ছে

ffigen ব্যবহার করার জন্য, আপনাকে LLVM ইনস্টল করতে হবে, যা ffigen C শিরোনাম পার্স করতে ব্যবহার করে। উইন্ডোজে, নিম্নলিখিত কমান্ডটি চালান।

PS> winget install -e --id LLVM.LLVM
Found LLVM [LLVM.LLVM] Version 15.0.5
This application is licensed to you by its owner.
Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.
Downloading https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.5/LLVM-15.0.5-win64.exe
  ██████████████████████████████   277 MB /  277 MB
Successfully verified installer hash
Starting package install...
Successfully installed

আপনার উইন্ডোজ মেশিনে LLVM এর ইনস্টলেশন সম্পূর্ণ করতে আপনার বাইনারি অনুসন্ধান পাথে C:\Program Files\LLVM\bin যোগ করতে আপনার সিস্টেম পাথগুলি কনফিগার করুন। আপনি এটি সঠিকভাবে ইনস্টল করা হয়েছে কিনা তা নিম্নরূপ পরীক্ষা করতে পারেন।

PS> clang --version
clang version 15.0.5
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: C:\Program Files\LLVM\bin

উবুন্টুর জন্য, এলএলভিএম নির্ভরতা নিম্নরূপ ইনস্টল করা যেতে পারে। অন্যান্য লিনাক্স ডিস্ট্রিবিউশনে এলএলভিএম এবং ক্ল্যাং-এর জন্য অনুরূপ নির্ভরতা রয়েছে।

$ sudo apt install libclang-dev
[sudo] password for brett:
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  libclang-15-dev
The following NEW packages will be installed:
  libclang-15-dev libclang-dev
0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded.
Need to get 26.1 MB of archives.
After this operation, 260 MB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://archive.ubuntu.com/ubuntu kinetic/universe amd64 libclang-15-dev amd64 1:15.0.2-1 [26.1 MB]
Get:2 http://archive.ubuntu.com/ubuntu kinetic/universe amd64 libclang-dev amd64 1:15.0-55.1ubuntu1 [2962 B]
Fetched 26.1 MB in 7s (3748 kB/s)
Selecting previously unselected package libclang-15-dev.
(Reading database ... 85898 files and directories currently installed.)
Preparing to unpack .../libclang-15-dev_1%3a15.0.2-1_amd64.deb ...
Unpacking libclang-15-dev (1:15.0.2-1) ...
Selecting previously unselected package libclang-dev.
Preparing to unpack .../libclang-dev_1%3a15.0-55.1ubuntu1_amd64.deb ...
Unpacking libclang-dev (1:15.0-55.1ubuntu1) ...
Setting up libclang-15-dev (1:15.0.2-1) ...
Setting up libclang-dev (1:15.0-55.1ubuntu1) ...

উপরের হিসাবে, আপনি লিনাক্সে আপনার LLVM ইনস্টলেশনটি নিম্নরূপ পরীক্ষা করতে পারেন।

$ clang --version
Ubuntu clang version 15.0.2-1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

ffigen কনফিগার করা হচ্ছে

শীর্ষ-স্তরের pubpsec.yaml তৈরি করা টেমপ্লেটে ffigen প্যাকেজের পুরানো সংস্করণ থাকতে পারে। প্লাগইন প্রকল্পে ডার্ট নির্ভরতা আপডেট করতে নিম্নলিখিত কমান্ডটি চালান:

$ flutter pub upgrade --major-versions

এখন যেহেতু ffigen প্যাকেজ আপ-টু-ডেট, পরবর্তী কনফিগার করুন ffigen কোন ফাইলগুলি বাইন্ডিং ফাইল তৈরি করতে ব্যবহার করবে। নিম্নলিখিতগুলির সাথে মেলে আপনার প্রকল্পের ffigen.yaml ফাইলের বিষয়বস্তু পরিবর্তন করুন৷

ffigen.yaml

# Run with `flutter pub run ffigen --config ffigen.yaml`.
name: DuktapeBindings
description: |
  Bindings for `src/duktape.h`.

  Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`.
output: 'lib/duktape_bindings_generated.dart'
headers:
  entry-points:
    - 'src/duktape.h'
  include-directives:
    - 'src/duktape.h'
preamble: |
  // ignore_for_file: always_specify_types
  // ignore_for_file: camel_case_types
  // ignore_for_file: non_constant_identifier_names
comments:
  style: any
  length: full
ignore-source-errors: true

এই কনফিগারেশনে LLVM-এ পাস করার জন্য C শিরোনাম ফাইল, তৈরি করার জন্য আউটপুট ফাইল, ফাইলের শীর্ষে রাখার বিবরণ এবং লিন্ট সতর্কতা যোগ করার জন্য ব্যবহৃত একটি প্রস্তাবনা বিভাগ অন্তর্ভুক্ত রয়েছে।

ফাইলের শেষে একটি কনফিগারেশন আইটেম রয়েছে যা আরও ব্যাখ্যার যোগ্য। ffigen 11.0.0 সংস্করণ অনুসারে বাইন্ডিং জেনারেটর ডিফল্টভাবে বাইন্ডিং তৈরি করবে না যদি হেডার ফাইলগুলি পার্স করার সময় clang দ্বারা উত্পন্ন সতর্কতা বা ত্রুটি থাকে।

Duktape হেডার ফাইলগুলি, যেমন লেখা আছে, ম্যাকওএস-এ clang ট্রিগার করে সতর্কতা জেনারেট করে কারণ Duktape-এর পয়েন্টারগুলিতে শূন্যতা টাইপ স্পেসিফায়ারের অভাব রয়েছে। macOS এবং iOS Duktape কে সম্পূর্ণভাবে সমর্থন করার জন্য Duktape কোডবেসে এই ধরনের স্পেসিফায়ার যোগ করা প্রয়োজন। ইতিমধ্যে, আমরা ignore-source-errors পতাকাটিকে true হিসাবে সেট করে এই সতর্কতাগুলি উপেক্ষা করার সিদ্ধান্ত নিচ্ছি৷

একটি প্রোডাকশন অ্যাপ্লিকেশানে, আপনার অ্যাপ্লিকেশন পাঠানোর আগে আপনার সমস্ত কম্পাইলার সতর্কতাগুলি মুছে ফেলা উচিত। যাইহোক, ডুকটেপের জন্য এটি করা এই কোডল্যাবের সুযোগের বাইরে।

অন্যান্য কী এবং মান সম্পর্কে আরও বিশদ বিবরণের জন্য ffigen ডকুমেন্টেশন দেখুন।

আপনাকে Duktape ডিস্ট্রিবিউশন থেকে নির্দিষ্ট Duktape ফাইলগুলিকে সেই অবস্থানে অনুলিপি করতে হবে যেখানে ffigen কনফিগার করা হয়েছে সেগুলি খুঁজে পেতে।

$ cp duktape-2.7.0/src/duktape.c src/
$ cp duktape-2.7.0/src/duktape.h src/
$ cp duktape-2.7.0/src/duk_config.h src/

প্রযুক্তিগতভাবে, আপনাকে শুধুমাত্র ffigen এর জন্য duktape.h জুড়ে কপি করতে হবে, কিন্তু আপনি লাইব্রেরি তৈরি করতে CMake কনফিগার করতে চলেছেন যার তিনটিরই প্রয়োজন। নতুন বাইন্ডিং তৈরি করতে ffigen চালান:

$ flutter pub run ffigen --config ffigen.yaml
Running in Directory: '/home/brett/GitHub/codelabs/ffigen_codelab/step_05'
Input Headers: [./src/duktape.h]
[WARNING]: No definition found for declaration - (Cursor) spelling: duk_hthread, kind: 2, kindSpelling: StructDecl, type: 105, typeSpelling: struct duk_hthread, usr: c:@S@duk_hthread
[WARNING]: No definition found for declaration - (Cursor) spelling: duk_hthread, kind: 2, kindSpelling: StructDecl, type: 105, typeSpelling: struct duk_hthread, usr: c:@S@duk_hthread
[WARNING]: Generated declaration '__va_list_tag' start's with '_' and therefore will be private.
Finished, Bindings generated in /home/brett/GitHub/codelabs/ffigen_codelab/step_05/./lib/duktape_bindings_generated.dart

আপনি প্রতিটি অপারেটিং সিস্টেমে বিভিন্ন সতর্কতা দেখতে পাবেন। আপনি আপাতত এগুলি উপেক্ষা করতে পারেন, কারণ Duktape 2.7.0 Windows, Linux, এবং macOS-এ clang এর সাথে কম্পাইল করার জন্য পরিচিত।

CMake কনফিগার করা হচ্ছে

CMake একটি বিল্ড সিস্টেম জেনারেশন সিস্টেম। এই প্লাগইনটি অ্যান্ড্রয়েড, উইন্ডোজ এবং লিনাক্সের জন্য বিল্ড সিস্টেম তৈরি করতে CMake ব্যবহার করে জেনারেট করা ফ্লাটার বাইনারিতে Duktape অন্তর্ভুক্ত করে। আপনাকে নিম্নরূপ টেমপ্লেট জেনারেট করা CMake কনফিগারেশন ফাইলটি পরিবর্তন করতে হবে।

src/CMakeLists.txt

cmake_minimum_required(VERSION 3.10)

project(ffigen_app_library VERSION 0.0.1 LANGUAGES C)

add_library(ffigen_app SHARED
  duktape.c                     # Modify
)

set_target_properties(ffigen_app PROPERTIES
  PUBLIC_HEADER duktape.h       # Modify
  PRIVATE_HEADER duk_config.h   # Add
  OUTPUT_NAME "ffigen_app"      # Add
)

# Add from here...
if (WIN32)
set_target_properties(ffigen_app PROPERTIES
  WINDOWS_EXPORT_ALL_SYMBOLS ON
)
endif (WIN32)
# ... to here.

target_compile_definitions(ffigen_app PUBLIC DART_SHARED_LIB)

CMake কনফিগারেশন সোর্স ফাইল যোগ করে, এবং আরও গুরুত্বপূর্ণভাবে, ডিফল্টরূপে সমস্ত C চিহ্ন রপ্তানি করতে উইন্ডোজে জেনারেট করা লাইব্রেরি ফাইলের ডিফল্ট আচরণ পরিবর্তন করে। এটি ইউনিক্স-স্টাইলের লাইব্রেরিগুলিকে সাহায্য করার জন্য একটি CMake কাজ, যা Duktape হল, উইন্ডোজের জগতে।

lib/ffigen_app.dart এর বিষয়বস্তুকে নিম্নলিখিত দিয়ে প্রতিস্থাপন করুন।

lib/ffigen_app.dart

import 'dart:ffi';
import 'dart:io' show Platform;
import 'package:ffi/ffi.dart' as ffi;

import 'duktape_bindings_generated.dart';

const String _libName = 'ffigen_app';

final DynamicLibrary _dylib = () {
  if (Platform.isMacOS || Platform.isIOS) {
    return DynamicLibrary.open('$_libName.framework/$_libName');
  }
  if (Platform.isAndroid || Platform.isLinux) {
    return DynamicLibrary.open('lib$_libName.so');
  }
  if (Platform.isWindows) {
    return DynamicLibrary.open('$_libName.dll');
  }
  throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();

final DuktapeBindings _bindings = DuktapeBindings(_dylib);

class Duktape {
  Duktape() {
    ctx =
        _bindings.duk_create_heap(nullptr, nullptr, nullptr, nullptr, nullptr);
  }

  void evalString(String jsCode) {
    var nativeUtf8 = jsCode.toNativeUtf8();
    _bindings.duk_eval_raw(
        ctx,
        nativeUtf8.cast<Char>(),
        0,
        0 |
            DUK_COMPILE_EVAL |
            DUK_COMPILE_SAFE |
            DUK_COMPILE_NOSOURCE |
            DUK_COMPILE_STRLEN |
            DUK_COMPILE_NOFILENAME);
    ffi.malloc.free(nativeUtf8);
  }

  int getInt(int index) {
    return _bindings.duk_get_int(ctx, index);
  }

  void dispose() {
    _bindings.duk_destroy_heap(ctx);
    ctx = nullptr;
  }

  late Pointer<duk_hthread> ctx;
}

এই ফাইলটি ডাইনামিক লিঙ্ক লাইব্রেরি ফাইল লোড করার জন্য দায়ী ( .so লিনাক্স এবং অ্যান্ড্রয়েডের জন্য, উইন্ডোজের জন্য .dll ) এবং একটি মোড়ক প্রদানের জন্য যা অন্তর্নিহিত সি কোডে আরও ডার্ট ইডিওম্যাটিক ইন্টারফেস প্রকাশ করে।

যেহেতু এই ফাইলটি সরাসরি ffi প্যাকেজ আমদানি করে, তাই আপনাকে প্যাকেজটিকে dev_dependencies থেকে dependencies সরাতে হবে। এটি করার একটি সহজ উপায় হল নিম্নলিখিত কমান্ডটি চালানো:

$ dart pub add ffi

উদাহরণের main.dart এর বিষয়বস্তু নিচের দিয়ে প্রতিস্থাপন করুন।

example/lib/main.dart

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

const String jsCode = '1+2';

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

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late Duktape duktape;
  String output = '';

  @override
  void initState() {
    super.initState();
    duktape = Duktape();
    setState(() {
      output = 'Initialized Duktape';
    });
  }

  @override
  void dispose() {
    duktape.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    const textStyle = TextStyle(fontSize: 25);
    const spacerSmall = SizedBox(height: 10);
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Duktape Test'),
        ),
        body: Center(
          child: Container(
            padding: const EdgeInsets.all(10),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Text(
                  output,
                  style: textStyle,
                  textAlign: TextAlign.center,
                ),
                spacerSmall,
                ElevatedButton(
                  child: const Text('Run JavaScript'),
                  onPressed: () {
                    duktape.evalString(jsCode);
                    setState(() {
                      output = '$jsCode => ${duktape.getInt(-1)}';
                    });
                  },
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

আপনি এখন ব্যবহার করে আবার উদাহরণ অ্যাপ্লিকেশন চালাতে পারেন:

$ cd example
$ flutter run

আপনি এই মত চলমান অ্যাপ্লিকেশন দেখতে হবে:

একটি উইন্ডোজ অ্যাপ্লিকেশনে শুরু করা Duktape দেখাচ্ছে

একটি উইন্ডোজ অ্যাপ্লিকেশনে Duktape JavaScript আউটপুট দেখানো হচ্ছে

এই দুটি স্ক্রিনশট জাভাস্ক্রিপ্ট চালান বোতাম টিপে আগে এবং পরে দেখায়। এটি ডার্ট থেকে জাভাস্ক্রিপ্ট কোড কার্যকর করা এবং স্ক্রীনে ফলাফল প্রদর্শন করে।

অ্যান্ড্রয়েড

অ্যান্ড্রয়েড হল একটি লিনাক্স, কার্নেল-ভিত্তিক ওএস এবং এটি কিছুটা ডেস্কটপ লিনাক্স ডিস্ট্রিবিউশনের মতো। CMake বিল্ড সিস্টেম দুটি প্ল্যাটফর্মের মধ্যে বেশিরভাগ পার্থক্য লুকিয়ে রাখতে পারে। অ্যান্ড্রয়েড তৈরি এবং চালানোর জন্য, নিশ্চিত করুন যে অ্যান্ড্রয়েড এমুলেটর চলছে (বা অ্যান্ড্রয়েড ডিভাইস সংযুক্ত আছে)। অ্যাপটি চালান। যেমন:

$ cd example
$ flutter run -d emulator-5554

আপনি এখন Android এ চলমান উদাহরণ অ্যাপ্লিকেশন দেখতে হবে:

একটি অ্যান্ড্রয়েড এমুলেটরে শুরু হওয়া Duktape দেখাচ্ছে৷

একটি Android এমুলেটরে Duktape JavaScript আউটপুট দেখানো হচ্ছে

6. MacOS এবং iOS-এ Duktape ব্যবহার করা

এখন আপনার প্লাগইন ম্যাকওএস এবং আইওএস, দুটি ঘনিষ্ঠভাবে সম্পর্কিত অপারেটিং সিস্টেমে কাজ করার সময়। macOS দিয়ে শুরু করুন। যদিও CMake macOS এবং iOS সমর্থন করে, আপনি Linux এবং Android এর জন্য যে কাজটি করেছেন তা পুনরায় ব্যবহার করবেন না, কারণ macOS এবং iOS-এ Flutter লাইব্রেরি আমদানির জন্য CocoaPods ব্যবহার করে।

ক্লিনিং আপ

পূর্ববর্তী ধাপে আপনি Android, Windows এবং Linux-এর জন্য একটি কার্যকরী অ্যাপ্লিকেশন তৈরি করেছেন। যাইহোক, মূল টেমপ্লেট থেকে কয়েকটি ফাইল অবশিষ্ট আছে যেগুলি এখন আপনাকে পরিষ্কার করতে হবে। নিম্নরূপ এখন তাদের সরান.

$ rm src/ffigen_app.c
$ rm src/ffigen_app.h
$ rm ios/Classes/ffigen_app.c
$ rm macos/Classes/ffigen_app.c

macOS

macOS প্ল্যাটফর্মে ফ্লাটার C এবং C++ কোড আমদানি করতে CocoaPods ব্যবহার করে। এর মানে হল যে এই প্যাকেজটি CocoaPods নির্মাণ পরিকাঠামোতে একীভূত করা প্রয়োজন। পূর্ববর্তী ধাপে CMake-এর সাথে তৈরি করার জন্য আপনি ইতিমধ্যেই কনফিগার করা C কোডের পুনরায় ব্যবহার সক্ষম করতে, আপনাকে macOS প্ল্যাটফর্ম রানারে একটি একক ফরওয়ার্ডিং ফাইল যোগ করতে হবে।

macos/Classes/duktape.c

#include "../../src/duktape.c"

এই ফাইলটি সি প্রিপ্রসেসরের শক্তি ব্যবহার করে আপনার পূর্ববর্তী ধাপে সেট আপ করা নেটিভ সোর্স কোড থেকে সোর্স কোড অন্তর্ভুক্ত করতে। এটি কিভাবে কাজ করে তার আরো বিস্তারিত জানার জন্য macos/ffigen_app.podspec দেখুন।

এই অ্যাপ্লিকেশনটি চালানো এখন একই প্যাটার্ন অনুসরণ করে যা আপনি উইন্ডোজ এবং লিনাক্সে দেখেছেন।

$ cd example
$ flutter run -d macos

একটি macOS অ্যাপ্লিকেশনে শুরু হওয়া Duktape দেখাচ্ছে৷

একটি macOS অ্যাপ্লিকেশনে Duktape JavaScript আউটপুট দেখাচ্ছে৷

iOS

MacOS সেটআপের মতো, iOS-এর জন্যও একটি একক ফরওয়ার্ডিং C ফাইল যোগ করা প্রয়োজন।

ios/Classes/duktape.c

#include "../../src/duktape.c"

এই একক ফাইলের সাথে, আপনার প্লাগইনটি এখন iOS-এ চালানোর জন্য কনফিগার করা হয়েছে। যথারীতি চালান।

$ flutter run -d iPhone

একটি iOS সিমুলেটরে শুরু হওয়া Duktape দেখাচ্ছে৷

একটি iOS সিমুলেটরে Duktape JavaScript আউটপুট দেখানো হচ্ছে

অভিনন্দন! আপনি সফলভাবে পাঁচটি প্ল্যাটফর্মে নেটিভ কোড একত্রিত করেছেন। এটি একটি উদযাপনের জন্য ভিত্তি! হয়তো আরও কার্যকরী ইউজার ইন্টারফেস, যা আপনি পরবর্তী ধাপে তৈরি করবেন।

7. রিড ইভাল প্রিন্ট লুপ প্রয়োগ করুন

একটি প্রোগ্রামিং ভাষার সাথে ইন্টারঅ্যাক্ট করা একটি দ্রুত ইন্টারেক্টিভ পরিবেশে অনেক বেশি মজাদার। এই ধরনের পরিবেশের মূল বাস্তবায়ন ছিল LISP-এর Read Eval Print Loop (REPL)। আপনি এই ধাপে Duktape এর সাথে অনুরূপ কিছু বাস্তবায়ন করতে যাচ্ছেন।

জিনিস উত্পাদন প্রস্তুত করা

বর্তমান কোড যা Duktape C লাইব্রেরির সাথে ইন্টারঅ্যাক্ট করে তা অনুমান করে যে কিছুই ভুল হতে পারে না। ওহ, এবং পরীক্ষা চলাকালীন এটি ডুকটেপ ডায়নামিক লিঙ্ক লাইব্রেরি লোড করে না। এই ইন্টিগ্রেশন প্রোডাকশন প্রস্তুত করতে, আপনাকে lib/ffigen_app.dart এ কিছু পরিবর্তন করতে হবে।

lib/ffigen_app.dart

import 'dart:ffi';
import 'dart:io' show Platform;
import 'package:ffi/ffi.dart' as ffi;
import 'package:path/path.dart' as p;             // Add this import

import 'duktape_bindings_generated.dart';

const String _libName = 'ffigen_app';

final DynamicLibrary _dylib = () {
  if (Platform.isMacOS || Platform.isIOS) {
    // Add from here...
    if (Platform.environment.containsKey('FLUTTER_TEST')) {
      return DynamicLibrary.open('build/macos/Build/Products/Debug'
          '/$_libName/$_libName.framework/$_libName');
    }
    // ...to here.
    return DynamicLibrary.open('$_libName.framework/$_libName');
  }
  if (Platform.isAndroid || Platform.isLinux) {
    // Add from here...
    if (Platform.environment.containsKey('FLUTTER_TEST')) {
      return DynamicLibrary.open(
          'build/linux/x64/debug/bundle/lib/lib$_libName.so');
    }
    // ...to here.
    return DynamicLibrary.open('lib$_libName.so');
  }
  if (Platform.isWindows) {
    // Add from here...
    if (Platform.environment.containsKey('FLUTTER_TEST')) {
      return DynamicLibrary.open(p.canonicalize(
          p.join(r'build\windows\runner\Debug', '$_libName.dll')));
    }
    // ...to here.
    return DynamicLibrary.open('$_libName.dll');
  }
  throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();

final DuktapeBindings _bindings = DuktapeBindings(_dylib);

class Duktape {
  Duktape() {
    ctx =
        _bindings.duk_create_heap(nullptr, nullptr, nullptr, nullptr, nullptr);
  }

  // Modify this function
  String evalString(String jsCode) {
    var nativeUtf8 = jsCode.toNativeUtf8();
    final evalResult = _bindings.duk_eval_raw(
        ctx,
        nativeUtf8.cast<Char>(),
        0,
        0 |
            DUK_COMPILE_EVAL |
            DUK_COMPILE_SAFE |
            DUK_COMPILE_NOSOURCE |
            DUK_COMPILE_STRLEN |
            DUK_COMPILE_NOFILENAME);
    ffi.malloc.free(nativeUtf8);

    if (evalResult != 0) {
      throw _retrieveTopOfStackAsString();
    }

    return _retrieveTopOfStackAsString();
  }

  // Add this function
  String _retrieveTopOfStackAsString() {
    Pointer<Size> outLengthPtr = ffi.calloc<Size>();
    final errorStrPtr = _bindings.duk_safe_to_lstring(ctx, -1, outLengthPtr);
    final returnVal =
        errorStrPtr.cast<ffi.Utf8>().toDartString(length: outLengthPtr.value);
    ffi.calloc.free(outLengthPtr);
    return returnVal;
  }

  void dispose() {
    _bindings.duk_destroy_heap(ctx);
    ctx = nullptr;
  }

  late Pointer<duk_hthread> ctx;
}

ডায়নামিক লিঙ্ক লাইব্রেরি লোড করার কোডটি কেসটি পরিচালনা করার জন্য প্রসারিত করা হয়েছে যেখানে প্লাগইনটি একটি টেস্ট রানারে ব্যবহার করা হচ্ছে। এটি একটি ইন্টিগ্রেশন পরীক্ষা লিখতে সক্ষম করে যা এই APIটিকে একটি ফ্লাটার পরীক্ষা হিসাবে ব্যবহার করে৷ জাভাস্ক্রিপ্ট কোডের একটি স্ট্রিং মূল্যায়ন করার কোডটি সঠিকভাবে ত্রুটির শর্তগুলি পরিচালনা করার জন্য প্রসারিত করা হয়েছে, উদাহরণস্বরূপ অসম্পূর্ণ বা ভুল কোড৷ এই অতিরিক্ত কোডটি দেখায় যে কীভাবে স্ট্রিংগুলিকে বাইট অ্যারে হিসাবে ফেরত দেওয়া হয় এবং ডার্ট স্ট্রিংগুলিতে রূপান্তরিত করা প্রয়োজন এমন পরিস্থিতিতে কীভাবে পরিচালনা করা যায়।

প্যাকেজ যোগ করা হচ্ছে

একটি REPL তৈরি করার সময়, আপনি ব্যবহারকারী এবং Duktape JavaScript ইঞ্জিনের মধ্যে একটি মিথস্ক্রিয়া প্রদর্শন করবেন। ব্যবহারকারী কোডের লাইনে প্রবেশ করে, এবং Duktape হয় গণনার ফলাফল, বা একটি ব্যতিক্রম সহ প্রতিক্রিয়া জানায়। আপনার লিখতে প্রয়োজন বয়লারপ্লেট কোডের পরিমাণ কমাতে আপনি freezed ব্যবহার করবেন। আপনি থিমে প্রদর্শিত বিষয়বস্তুকে আরও একটু বেশি করতে google_fonts এবং রাষ্ট্রীয় ব্যবস্থাপনার জন্য flutter_riverpod ব্যবহার করবেন।

উদাহরণ অ্যাপে প্রয়োজনীয় নির্ভরতা যোগ করুন:

$ cd example
$ dart pub add flutter_riverpod freezed_annotation google_fonts
$ dart pub add -d build_runner freezed

পরবর্তী, REPL মিথস্ক্রিয়া রেকর্ড করতে একটি ফাইল তৈরি করুন:

example/lib/duktape_message.dart

import 'package:freezed_annotation/freezed_annotation.dart';

part 'duktape_message.freezed.dart';

@freezed
class DuktapeMessage with _$DuktapeMessage {
  factory DuktapeMessage.evaluate(String code) = DuktapeMessageCode;
  factory DuktapeMessage.response(String result) = DuktapeMessageResponse;
  factory DuktapeMessage.error(String log) = DuktapeMessageError;
}

এই ক্লাসটি freezed ইউনিয়ন টাইপ বৈশিষ্ট্য ব্যবহার করে REPL-এ প্রদর্শিত প্রতিটি লাইনের আকারের তিনটি প্রকারের মধ্যে একটি হিসাবে সহজে প্রকাশ করতে সক্ষম হয়। এই মুহুর্তে, আপনার কোড সম্ভবত এই কোডে কিছু ত্রুটি দেখাচ্ছে, কারণ অতিরিক্ত কোড তৈরি করা দরকার। নিচের মত করে এখন সেটা করুন।

$ flutter pub run build_runner build

এটি example/lib/duktape_message.freezed.dart ফাইল তৈরি করে, যে কোডটি আপনি এইমাত্র টাইপ করেছেন তার উপর নির্ভর করে।

এর পরে, ফন্ট ডেটার জন্য নেটওয়ার্ক অনুরোধ করতে google_fonts সক্ষম করতে আপনাকে macOS কনফিগারেশন ফাইলগুলিতে এক জোড়া পরিবর্তন করতে হবে।

example/macos/Runner/DebugProfile.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>com.apple.security.app-sandbox</key>
        <true/>
        <key>com.apple.security.cs.allow-jit</key>
        <true/>
        <key>com.apple.security.network.server</key>
        <true/>
        <!-- Add from here... -->
        <key>com.apple.security.network.client</key>
        <true/>
        <!-- ...to here -->
</dict>
</plist>

example/macos/Runner/Release.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>com.apple.security.app-sandbox</key>
        <true/>
        <!-- Add from here... -->
        <key>com.apple.security.network.client</key>
        <true/>
        <!-- ...to here -->
</dict>
</plist>

REPL নির্মাণ

এখন যেহেতু আপনি ত্রুটিগুলি পরিচালনা করতে ইন্টিগ্রেশন লেয়ার আপডেট করেছেন, এবং আপনি ইন্টারঅ্যাকশনের জন্য একটি ডেটা উপস্থাপনা তৈরি করেছেন, এটি উদাহরণ অ্যাপের ইউজার ইন্টারফেস তৈরি করার সময়।

example/lib/main.dart

import 'package:ffigen_app/ffigen_app.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';

import 'duktape_message.dart';

void main() {
  runApp(const ProviderScope(child: DuktapeApp()));
}

final duktapeMessagesProvider =
    StateNotifierProvider<DuktapeMessageNotifier, List<DuktapeMessage>>((ref) {
  return DuktapeMessageNotifier(messages: <DuktapeMessage>[]);
});

class DuktapeMessageNotifier extends StateNotifier<List<DuktapeMessage>> {
  DuktapeMessageNotifier({required List<DuktapeMessage> messages})
      : duktape = Duktape(),
        super(messages);
  final Duktape duktape;

  void eval(String code) {
    state = [
      DuktapeMessage.evaluate(code),
      ...state,
    ];
    try {
      final response = duktape.evalString(code);
      state = [
        DuktapeMessage.response(response),
        ...state,
      ];
    } catch (e) {
      state = [
        DuktapeMessage.error('$e'),
        ...state,
      ];
    }
  }
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Duktape App',
      home: DuktapeRepl(),
    );
  }
}

class DuktapeRepl extends ConsumerStatefulWidget {
  const DuktapeRepl({
    super.key,
  });

  @override
  ConsumerState<DuktapeRepl> createState() => _DuktapeReplState();
}

class _DuktapeReplState extends ConsumerState<DuktapeRepl> {
  final _controller = TextEditingController();
  final _focusNode = FocusNode();
  var _isComposing = false;

  void _handleSubmitted(String text) {
    _controller.clear();
    setState(() {
      _isComposing = false;
    });
    setState(() {
      ref.read(duktapeMessagesProvider.notifier).eval(text);
    });
    _focusNode.requestFocus();
  }

  @override
  Widget build(BuildContext context) {
    final messages = ref.watch(duktapeMessagesProvider);
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: const Text('Duktape REPL'),
        elevation: Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0,
      ),
      body: Column(
        children: [
          Flexible(
            child: Ink(
              color: Theme.of(context).scaffoldBackgroundColor,
              child: SafeArea(
                bottom: false,
                child: ListView.builder(
                  padding: const EdgeInsets.all(8.0),
                  reverse: true,
                  itemBuilder: (context, idx) => messages[idx].when(
                    evaluate: (str) => Padding(
                      padding: const EdgeInsets.symmetric(vertical: 2),
                      child: Text(
                        '> $str',
                        style: GoogleFonts.firaCode(
                          textStyle: Theme.of(context).textTheme.titleMedium,
                        ),
                      ),
                    ),
                    response: (str) => Padding(
                      padding: const EdgeInsets.symmetric(vertical: 2),
                      child: Text(
                        '= $str',
                        style: GoogleFonts.firaCode(
                          textStyle: Theme.of(context).textTheme.titleMedium,
                          color: Colors.blue[800],
                        ),
                      ),
                    ),
                    error: (str) => Padding(
                      padding: const EdgeInsets.symmetric(vertical: 2),
                      child: Text(
                        str,
                        style: GoogleFonts.firaCode(
                          textStyle: Theme.of(context).textTheme.titleSmall,
                          color: Colors.red[800],
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ),
                  ),
                  itemCount: messages.length,
                ),
              ),
            ),
          ),
          const Divider(height: 1.0),
          SafeArea(
            top: false,
            child: Container(
              decoration: BoxDecoration(color: Theme.of(context).cardColor),
              child: _buildTextComposer(),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildTextComposer() {
    return IconTheme(
      data: IconThemeData(color: Theme.of(context).colorScheme.secondary),
      child: Container(
        margin: const EdgeInsets.symmetric(horizontal: 8.0),
        child: Row(
          children: [
            Text('>', style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(width: 4),
            Flexible(
              child: TextField(
                controller: _controller,
                decoration: const InputDecoration(
                  border: InputBorder.none,
                ),
                onChanged: (text) {
                  setState(() {
                    _isComposing = text.isNotEmpty;
                  });
                },
                onSubmitted: _isComposing ? _handleSubmitted : null,
                focusNode: _focusNode,
              ),
            ),
            Container(
              margin: const EdgeInsets.symmetric(horizontal: 4.0),
              child: IconButton(
                icon: const Icon(Icons.send),
                onPressed: _isComposing
                    ? () => _handleSubmitted(_controller.text)
                    : null,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

এই কোডটিতে অনেক কিছু চলছে, তবে এটি সমস্ত ব্যাখ্যা করা এই কোডল্যাবের সুযোগের বাইরে। আমি আপনাকে কোডটি চালানোর পরামর্শ দিচ্ছি, এবং তারপরে যথাযথ ডকুমেন্টেশন পর্যালোচনা করার পরে কোডে পরিবর্তন করুন।

$ cd example
$ flutter run

Duktape REPL একটি লিনাক্স অ্যাপ্লিকেশনে চলছে

Duktape REPL একটি উইন্ডোজ অ্যাপ্লিকেশনে চলছে

Duktape REPL একটি iOS সিমুলেটরে চলছে

Duktape REPL একটি Android এমুলেটরে চলছে

8. অভিনন্দন

অভিনন্দন! আপনি Windows, macOS, Linux, Android, এবং iOS-এর জন্য সফলভাবে একটি Flutter FFI-ভিত্তিক প্লাগইন তৈরি করেছেন!

আপনি একটি প্লাগইন তৈরি করার পরে, আপনি এটি অনলাইনে শেয়ার করতে চাইতে পারেন যাতে অন্যরা এটি ব্যবহার করতে পারে। আপনি ডেভেলপিং প্লাগইন প্যাকেজে pub.dev- তে আপনার প্লাগইন প্রকাশ করার সম্পূর্ণ ডকুমেন্টেশন পেতে পারেন।