Integrasi Flutter dengan Library Reader Handheld §
Belakangan ini, Flutter telah menjadi salah satu framework UI open-source populer pada berbagai platform, termasuk perangkat mobile seperti Android dan iOS, serta platform lainnya seperti web dan desktop. Flutter menawarkan kemudahan dalam pembuatan antarmuka pengguna (UI) yang menarik dan performa tinggi dengan satu basis kode.
Dalam tutorial kali ini, akan membahas bagaimana SDK Reader handheld Electron dapat diintegrasikan dan digunakan dalam Flutter.
Gambaran Umum §
Hampir semua reader Electron menggunakan bahasa Java untuk SDK dan contoh program demo tersedia.
Komunikasi Flutter memanggil fungsi-fungsi Reader tetap membutuhkan kode Java. Memanggil kode Java dengan Dart (yang berjalan di Flutter) mekanisme ini dinamakan PlatformChannel. PlatformChannel memungkinkan aplikasi Flutter untuk memanfaatkan fungsi atau fitur yang tidak tersedia di Flutter, tetapi tersedia di platform native, seperti akses ke sensor, Bluetooth, atau API platform lainnya.
Dengan demikian, Flutter akan dijadikan sebagai UI dan pengolahan data, sedangkan untuk memanggil fungsi Reader akan dilakukan oleh kode Java dipanggil dengan kode Dart melalui PlatformChannel.
Terdapat 2 method yang dapat digunakan dalam proses pemanggilan kode Java:
MethodChannel
§
Jenis komunikasi Request-Response. Kode Dart akan request, kode Java akan berikan 1x response.
Pada kasus reader, cocok digunakan untuk inventory answer mode, write tag, atau get/set reader settings.
pigeon | Package salah satu Flutter package yang dapat mengenerate kode MethodChannel, dapat mempercepat development dan type-safe.
EventChannel
§
Jenis komunikasi Stream (continuous). Data akan dikirim berkelanjutan dari kode Java. Kode Dart hanya akan listen tanpa mengirim data.
Pada kasus reader, cocok digunakan untuk listen bluetooth terputus, atau ketika user menekan fire button (hard button pistol) agar dapat mengeksekusi kode Dart.
Lebih lanjut dapat dipelajari di dokumentasi resmi Flutter Platform-specific code .
Tutorial §
Buat flutter project dengan spesifik bahasa Android ke bahasa Java:
flutter create --a java flutter_tutorial_el_uhf_rh02
Import Library §
Import/paste folder & file library ke folder/android/app/
:
/android/app/libs
: Berisi file *.jar
/android/app/src/main/jniLibs
: Berisi file *.so
Library *.jar
dan *.so
, silahkan diambil pada contoh program demo reader yang Anda beli (beda reader beda Library SDK).
Pada file /android/app/build.gradle
tentukan folder library *.jar
berada
...
// Add /.jar file
dependencies {
implementation fileTree (include : [ '*.jar' ], dir : 'libs' )
}
Tambahkan file /android/app/proguard-rules.pro
, agar saat build release class-class Library yang dibutuhkan tetap dipertahankan:
-keep class com.handheld.** { *; }
-keep class com.uhf.api.** { *; }
-keep class cn.pda.serialport.** { *; }
Sesuaikan isi file proguard-rules.pro
pada program demo yang tersedia (beda reader beda isi file).
Daftarkan file proguard-rules.pro
pada file /android/app/build.gradle
:
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile ( 'proguard-android.txt' ), 'proguard-rules.pro' // Add proguard-rules.pro
}
}
Setelah semua ditambahkan, tree folder /android/app/
akan terlihat seperti berikut:
.
├── build.gradle
├── libs
│ ├── ...*.jar
├── proguard-rules.pro
└── src
├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ ├── com
│ │ │ └── example
│ │ │ └── flutter_tutorial_el_uhf_rh02
│ │ │ └── MainActivity.java
│ │ └── io
│ │ └── flutter
│ │ └── plugins
│ │ ├── GeneratedPluginRegistrant.java
│ ├── jniLibs
│ │ ├── arm64-v8a
│ │ │ ├── ...*.so
│ │ ├── armeabi
│ │ │ ├── ...*.so
│ │ └── armeabi-v7a
│ │ ├── ...*.so
│ └── res
│ ├── drawable
...
Code §
Untuk kode lebih lengkap, dapat di download pada link berikut:
https://cloud.electron.id/s/DJJLDn554opWeSX
Pada tutorial kali ini kami menggunakan pigeon | Package , pertama buat folder pigeons/
sejajar dengan folder lib/
Flutter.
Buat file pigeons/reader.dart
:
import 'package:pigeon/pigeon.dart' ;
class ReaderPower {
int ? readPower;
int ? writePower;
}
class InventoryResult {
String ? epc;
int ? rssi;
}
@HostApi ()
abstract class ReaderApi {
@async
bool init ();
@async
ReaderPower ? getPower ();
@async
bool setPower ( int readPower, int writePower);
@async
bool writeEpc ( String currentEpc, String toWriteEpc, String accessPassowrd);
@async
List < InventoryResult ?> inventory ();
@async
bool dispose ();
}
Buat fungsi’ yang ingin direquest oleh Flutter, pada contoh hanya ada 6, silahakan tambahkan sesuai keperluan:
init
: inisialisasi
inventory
: inventory tag
getPower
: ambil data reader power
setPower
: atur reader power
writeEpc
: write EPC memory
dispose
: close library
Jalankan command berikut untuk mengenerate kode Flutter reader.dart
& Java Pigeon.java
:
flutter pub run pigeon --input pigeons/reader.dart --dart_out lib/services/reader.dart --java_out ./android/app/src/main/java/io/flutter/plugins/Pigeon.java --java_package "io.flutter.plugins"
MainActivity.java
§
Lakukan pengkodean Java dengan import Pigeon.Java
hasil generate.
// MainActivity.java
package com.example.flutter_tutorial_el_uhf_rh02;
import android.view.KeyEvent;
import android.util.Log;
import android.os.Handler;
import android.os.Looper;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import androidx.annotation.NonNull;
import cn.pda.serialport.Tools;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.EventChannel.EventSink;
import io.flutter.plugin.common.EventChannel.StreamHandler;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import com.handheld.uhfr.UHFRManager;
import com.uhf.api.cls.Reader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugins.Pigeon.Result;
import io.flutter.plugins.Pigeon.NullableResult;
import io.flutter.plugins.Pigeon.ReaderApi;
import io.flutter.plugins.Pigeon.InventoryResult;
import io.flutter.plugins.Pigeon.ReaderPower;
public class MainActivity extends FlutterActivity {
private static final String TAG = "MainActivity" ;
private static UHFRManager uhfrManager;
private BinaryMessenger messenger;
private EventChannel fireButtonEventChannel;
private EventSink fireButtonEventSink;
@ Override
public void configureFlutterEngine (@ NonNull FlutterEngine flutterEngine ) {
super . configureFlutterEngine (flutterEngine);
Log. d (TAG, "configureFlutterEngine" );
String packageName = getApplicationContext (). getPackageName ();
messenger = flutterEngine. getDartExecutor (). getBinaryMessenger ();
// Set up Pigeon-generated ReaderApi
ReaderApi. setUp (messenger, new ReaderApiHandler ());
// Set up EventChannel for fire button
fireButtonEventChannel = new EventChannel (messenger, packageName + "/fire_button" );
fireButtonEventChannel. setStreamHandler (fireButtonStreamHandler);
}
// Handler class for ReaderApi methods
public class ReaderApiHandler implements ReaderApi {
// Init - Init library variable (UHFRManager) and check if connect to reader
@ Override
public void init (Result< Boolean > callback ) {
Log. d (TAG, "ReaderApiHandler.init" );
uhfrManager = UHFRManager. getInstance ();
if (uhfrManager == null || uhfrManager. getHardware () == null ) {
callback. error ( new Exception ( "Failed to initialize reader (can't get hardware version)" ));
return ;
}
uhfrManager. setGen2session ( false );
callback. success ( true );
}
// Inventory
@ Override
public void inventory (Result<List< InventoryResult >> callback ) {
new Thread (() -> {
uhfrManager. setCancleInventoryFilter ();
uhfrManager. setGen2session ( false );
List< InventoryResult > inventoryResults = new ArrayList<>();
List< Reader . TAGINFO > tags = uhfrManager. tagInventoryByTimer (( short ) 100 );
if (tags != null ) {
for (Reader.TAGINFO tag : tags) {
InventoryResult inventoryResult = new InventoryResult ();
inventoryResult. setEpc (Tools. Bytes2HexString (tag.EpcId, tag.EpcId.length));
inventoryResult. setRssi (( long ) tag.RSSI);
inventoryResults. add (inventoryResult);
}
}
callback. success (inventoryResults);
}). start ();
}
// Get power
@ Override
public void getPower (NullableResult< ReaderPower > callback ) {
int [] power = uhfrManager. getPower ();
if (power != null ) {
ReaderPower readerPower = new ReaderPower ();
readerPower. setReadPower (( long ) power[ 0 ]);
readerPower. setWritePower (( long ) power[ 1 ]);
callback. success (readerPower);
return ;
}
callback. error ( new Exception ( "Failed to get reader power" ));
}
// Set power
@ Override
public void setPower (Long readPower , Long writePower , Result< Boolean > callback ) {
Reader.READER_ERR response = uhfrManager. setPower (readPower. intValue (), writePower. intValue ());
if (response == Reader.READER_ERR.MT_OK_ERR) {
callback. success ( true );
return ;
}
callback. error ( new Exception ( "Failed to set reader power" ));
}
// Write EPC
@ Override
public void writeEpc (String currentEpc , String toWriteEpc , String accessPassword ,
Result< Boolean > callback ) {
new Thread (() -> {
byte [] currentEpcBytes = Tools. HexString2Bytes (currentEpc);
byte [] toWriteEpcBytes = Tools. HexString2Bytes (toWriteEpc);
byte [] accessPasswordBytes = Tools. HexString2Bytes (accessPassword);
Reader.READER_ERR response = uhfrManager. writeTagDataByFilter (( char ) 1 , 2 , toWriteEpcBytes,
toWriteEpcBytes.length, accessPasswordBytes, ( short ) 3000 ,
currentEpcBytes, 1 , 2 , true );
if (response == Reader.READER_ERR.MT_OK_ERR) {
callback. success ( true );
return ;
}
callback. success ( false );
}). start ();
}
// Dispose, close the library variable
@ Override
public void dispose (Result< Boolean > callback ) {
uhfrManager. close ();
callback. success ( true );
}
}
// StreamHandler for the fire button
private final StreamHandler fireButtonStreamHandler = new StreamHandler () {
private final BroadcastReceiver fireButtonReceiver = new BroadcastReceiver () {
@ Override
public void onReceive (Context context , Intent intent ) {
int keyCode = intent. getIntExtra ( "keyCode" , intent. getIntExtra ( "keycode" , 0 ));
boolean keyDown = intent. getBooleanExtra ( "keydown" , false );
if (keyCode == KeyEvent.KEYCODE_F4) {
Map< String , Object > keyShootMap = new HashMap<>();
keyShootMap. put ( "key_code" , keyCode);
keyShootMap. put ( "key_down" , keyDown);
fireButtonEventSink. success (keyShootMap);
}
}
};
@ Override
public void onListen (Object args , EventSink eventSink ) {
fireButtonEventSink = new EventSinkWrapper (eventSink);
IntentFilter filter = new IntentFilter ( "android.rfid.FUN_KEY" );
registerReceiver (fireButtonReceiver, filter);
Log. d (TAG, "fireButtonStreamHandler.onListen" );
}
@ Override
public void onCancel (Object args ) {
fireButtonEventSink = null ;
Log. d (TAG, "fireButtonStreamHandler.onCancel" );
}
};
// Wrapper for EventSink to ensure execution on the main thread
private static class EventSinkWrapper implements EventSink {
private final EventSink eventSink;
private final Handler handler = new Handler (Looper. getMainLooper ());
EventSinkWrapper (EventSink eventSink ) {
this .eventSink = eventSink;
}
@ Override
public void success (Object result ) {
handler. post (() -> eventSink. success (result));
}
@ Override
public void error (@ NonNull String errorCode , String errorMessage , Object errorDetails ) {
handler. post (() -> eventSink. error (errorCode, errorMessage, errorDetails));
}
@ Override
public void endOfStream () {
handler. post (eventSink :: endOfStream);
}
}
}
configureFlutterEngine
: method bawaan FlutterActivity
setiap aplikasi mulai dijakankan, bisa untuk inisialiasi varible Java.
ReaderApiHandler implements ReaderApi
: panggil fungsi’ library di dalam class ini, override tiap method yang sudah ada di Pigeon.Java
.
StreamHandler fireButtonStreamHandler
untuk Flutter listen EventChannel user menekan fire button (hard button pistol).
Flutter §
Untuk Flutter tinggal memanggil class ReaderApi()
hasil generate dari library pigeon | Package , pada contoh letaknya di lib/services/reader.dart
.
Panggil class tersebut seperti proses memanggil service logic request Database atau request API Endpoint.
Pada contoh kami menggunakan state management bloc
(cubit), sehingga proses memanggil class ReaderApi()
dan EventChannel
terjadi di class state management tersebut.
Video Demo §