Flutter proper way to test dart:ui on Android device - flutter

I faced following problem:
My Android Flutter app has a serie of dart:ui calls, basically to do an application files backup. Lets just check how to test that it find the correct path of the app files. The function to test:
static Future<String> getUserPath() async {
final appDocDir = await getApplicationDocumentsDirectory();
final pathComponents = path.split(appDocDir.path);
pathComponents.removeLast();
final filepaths = path.join(path.joinAll(pathComponents), 'files', '.user');
if (!await Directory(filepaths).exists()) throw FileSystemException('User directory not found');
return filepaths;
}
Easy, it call different dart:ui functions to check where the user files are. It works now, but I want to write the test for this funcion. Basically I found two ways to test this is: using Flutter Drive tests to write integration tests, i followed this tutorial: https://flutter-website-staging.firebaseapp.com/testing/ . And using Flutter integration tests, using this guide: https://docs.flutter.dev/testing/integration-tests.
The test I wrote is next:
testWidgets('test_correct_path', (WidgetTester tester) async {
print('user folder: ' + await getUserPath());
assert('.user' == basename(await getUserPath()));
});
Using driver tests
I created the following folder on my project:
test_driver
├── backup.dart
└── backup_test.dart
On backup.dart
// This line imports the extension
import 'package:flutter_driver/driver_extension.dart';
import 'package:elRepoIo/main.dart' as app;
void main() {
// This line enables the extension
enableFlutterDriverExtension();
}
And on backup_test.dart I just added the needed functions after test main declaration:
void main() {
FlutterDriver driver;
// connect flutter driver to the app before executing the runs
setUpAll(() async {
driver = await FlutterDriver.connect();
});
// disconnect flutter driver from the app after executing the runs
tearDownAll(() async {
if (driver != null) {
driver.close();
}
});
// Tests down here
Then when I execute the tests using flutter drive --target=test_driver/backup.dart I receive a bunch of errors such flutter/packages/flutter_test/lib/src/frame_timing_summarizer.dart:5:8: Error: Not found: 'dart:ui' import 'dart:ui';
Using integration tests
Under
integration_test
├── backup_test.dart
I wrote the tests adding the following line:
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); // NEW
Then I run the test using flutter test integration_test/backup_test.dart -d $device_id where I get device_id using flutter devices.
If I debug this I found that the path is correct, but await Directory(filepaths).exists() is false.
It seem that if I run the app to create the folder and inmetially I run the tests the directory exist. Are integration tests deleting all file data? And how can I keep it?

Related

Can I run a CLI command inside a flutter project

When I click the Boilerplate button I want to run the CLI command "flutter create projectName" (inside a flutter project/dart file)
I have already heard about
process.run
import 'dart:io';
main() async {
// List all files in the current directory in UNIX-like systems.
var result = await Process.run('ls', ['-l']);
print(result.stdout);
}
&
shell
var shell = Shell();
await shell.run('''
echo "hello world"
''');
But I don't know how to use in this situation

Flutter Web - Save ui.Image to png File

I need some help with my code, I use the
"FilePicker.platform.saveFile()"
function to get the path on Windows Version (but not works on Web) and then
"final imageFuture = await controller
.renderImage(backgroundImageSize)
.then<Uint8List?>((ui.Image image) => image.pngBytes);
final imgFile = File(path);
imgFile.writeAsBytesSync(imageFuture!);
"
to save my image to disk, this works perfectly on Windows, but not on the web version.I need an alternative to save ui.Image (web Version) to PNG File on disk. 🙏🙏
I was able to solve my problem only, however the solution works only in release mode (after the build process).
import 'dart:html' as html;
import 'dart:js' as js;
final backgroundImageSize = Size(backgroundImage!.width.toDouble(),
backgroundImage!.height.toDouble());
final imageFuture = await controller
.renderImage(backgroundImageSize)
.then<Uint8List?>((ui.Image image) => image.pngBytes);
(imageFuture != null)
? js.context.callMethod(
"saveAs",
[
html.Blob([imageFuture]),
'output-image.png',
],
)
: print('Image is null');
It is necessary to add inside your web/index.html file:
<script src="https://cdnjs.cloudflare.com/ajax/libs/amcharts/3.21.15/plugins/export/libs/FileSaver.js/FileSaver.min.js"></script>

Unable to get AppCheck (Debug on Emulator) to work on a Flutter application

I have a flutter application, adding AppCheck and using Android Emulator to test and debug.
I am testing the access of Realtime database. From my Firebase Console, AppCheck shows that all my access are of this type: Unverified: invalid requests. I have followed this: https://firebase.google.com/docs/app-check/android/debug-provider.
my app/build.gradle
dependencies {
...
//implementation 'com.google.firebase:firebase-appcheck-safetynet:16.0.0-beta02'
implementation 'com.google.firebase:firebase-appcheck-debug:16.0.0-beta03'
...
}
In my main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Initialize AppCheck
await FirebaseAppCheck.instance.activate();
...
In MainActivity.kt, I have the following:
import io.flutter.embedding.android.FlutterActivity
import android.os.Bundle
import android.util.Log
import com.google.firebase.FirebaseApp
import com.google.firebase.appcheck.FirebaseAppCheck
import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory
//import com.google.firebase.appcheck.safetynet.SafetyNetAppCheckProviderFactory
class MainActivity: FlutterActivity() {
// For Debug Only. Do not do this for Production
override fun onCreate(savedInstanceState: Bundle?) {
FirebaseApp.initializeApp(this)
Log.e("MainActivity", "onCreate")
val firebaseAppCheck = FirebaseAppCheck.getInstance()
firebaseAppCheck.installAppCheckProviderFactory(DebugAppCheckProviderFactory.getInstance())
super.onCreate(savedInstanceState)
}
}
From logcat, I can see the following log
com.google.firebase.appcheck.debug.internal.DebugAppCheckProvider: Enter this debug secret into the allow list in the Firebase Console for your project: xxxxxxxxxxxxx
Based on the token, I use managed debug token and set it to a debug token.
Using the AppCheck
Realtime Database only shows unverified requests
I am expecting to see verified requests showing up.
I also use Android Studio profiler to monitor the Network, I can see a request
POST https://firebaseappcheck.googleapis.com/v1beta/projects/<app>/apps/<appid>:exchangeSafetyNetToken?key=<key>
In the payload is a JSON safetynet token.
I get a response of 403.
Note that I have not turn on enforcement on the realtime database.
What am I missing with AppCheck? Am I supposed to see verified request using the emulator or only on real physical device (release mode)?
I tried with onCreate but couldn't get it to work.
Using a MethodChannel worked instead 🎉:
// main.dart
void main() async {
// ...
await Firebase.initializeApp();
await FirebaseAppCheck.instance.activate();
// FirebaseAppCheck when enforced would block incoming requests from Android emulator and iOS simulator too.
// This kDebugMode check gets a debug token from FirebaseAppCheck which can then be added on the Firebase
// console so that the emulator and simulator can be allowed to access to Firestore.
if (kDebugMode) {
try {
const MethodChannel methodChannel = MethodChannel("method-channel");
await methodChannel.invokeMethod("getFirebaseAppCheckDebugToken");
} catch (e) {
print("FirebaseAppCheck debug token error: $e");
}
}
// ...
}
// MainActivity.kt
package com.yourname.applicationname
import android.util.Log
import androidx.annotation.NonNull
import com.google.firebase.FirebaseApp
import com.google.firebase.appcheck.FirebaseAppCheck
import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "method-channel").setMethodCallHandler { call, result ->
if (call.method == "getFirebaseAppCheckDebugToken") {
FirebaseApp.initializeApp(this)
Log.d("configureFlutterEngine", "FirebaseApp.initializeApp")
val firebaseAppCheck = FirebaseAppCheck.getInstance()
Log.d("configureFlutterEngine", "firebaseAppCheck")
firebaseAppCheck.installAppCheckProviderFactory(DebugAppCheckProviderFactory.getInstance())
Log.d("configureFlutterEngine", "installAppCheckProviderFactory")
result.success("Yay!")
}
}
}
}
Result on every app launch on the Android emulator in Flutter debug mode:
I managed to workaround this issue (in debug mode) by adding the following condition:
if (kReleaseMode) {
await FirebaseAppCheck.instance.activate();
}
To ensure that AppCheck works in your flutter project make sure you have the firebase_app_check package installed. Once you do you can use it as such:
import 'package:firebase_app_check/firebase_app_check.dart';
import 'package:flutter/foundation.dart';
main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// AppCheck
await FirebaseAppCheck.instance.activate(
androidProvider: kDebugMode ? AndroidProvider.debug : AndroidProvider.playIntegrity
);
// The rest of your code
}
Using the ternary operator above allows you to set it and forget it. Just remember to enable Enable App Check enforcement to get things running.
Things to remember:
Dependencies. Make sure you have implementation platform('com.google.firebase:firebase-bom:31.2.0') in your dependencies in app/build.gradle file (31.2.0 as of writing). When you have bom installed you don't need to import each firebase service individually. The bom takes care of that for you.
You can compare bom versions here.
AppCheck will only work in release builds. For dev use, there's the debug code. Good thing is you only need to set the debug code for every new install per device. That usually means you can keep creating new builds for as long as you don't uninstall it from your emulator. But it never hurts to remember the first few characters just in case.
This may be an isolated case but if appcheck still won't work in prod try installing from Playstore by creating a new release.
Importing package:flutter/foundation.dart gives you access to kDebugMode

Null check operator error with tflite Flutter

The following code causes this error Null check operator used on a null value but I cannot figure out why.
import 'package:tflite_flutter/tflite_flutter.dart';
final modelFile = 'model.tflite';
void main() async {
foo();
}
void foo() async {
Interpreter inter = await Interpreter.fromAsset(modelFile);
}
The model exists at the correct location and I have tried multiple other locations just in case. The file is in my pubspec.yaml and I have tried multiple variations just in case. Not sure if I need to provide more parameters or something but I can't figure it out. Any help would be appreciated.
Just to test if it was an issue with me not placing the file in the correct location, or not referencing it in the pubspec.yaml I tested other assets such as Images and Text files in the same location and they loaded perfectly fine.
It looks like you have not exported the file. To fix to go to pubspec.yaml and find these lines:
flutter:
assets:
- model.tflite
And make sure you are specifying the correct file location when adding it through pubscpec. After that uninstall the app, do flutter clean, flutter pub get and it should work
I assumer the directory containing the model is named as "assets".
import 'package:tflite_flutter/tflite_flutter.dart';
final modelFile = 'assets/model.tflite';
void main() async {
foo();
}
void foo() async {
Interpreter inter = await Interpreter.fromAsset(modelFile);
}
Code for pubspec:-
flutter:
assets:
- assets/model.tflite

Separate development from deployment assets in Flutter

I am using some assets during my development (fake API responses and random images), and for that, I am loading some assets.
Obviously I do not want those assets to be bundled with the release APK, how do you come around doing so?
For now, I have all assets in one pubspec.yaml.
The end goal is to be able to run the app flutter run and have fake backend. I know of mocking HTTP calls, but it is very difficult to wrap a full app, so now I have two different data source (one from assets and another http-based), and I call the one depending on kReleaseMode value.
Thanks!
I had the same problem (which also seems to be an orphaned flutter issue), but solved it using a Builder running in the dart build_runner - not to be confused with the flutter Builder pattern for widgets (which spoils the search results). This works as sketched here:
collect dev-resources in a separate directory out of assets (in my case lib/devdata/)
create a Builder implementation and a build.yaml file
in build.yaml create separate dev_properties that allow the builder to behave according to the build mode, i.e. either copy the files to assets/devdata/ or delete them in assets/devdata/ and this directory to .gitignore
add assets/devdata/ to assets in pubspec.yaml
run build_runner either in dev or in prod mode to copy or delete the files
consume the files in the app using rootBundle if they are present / catch the resulting exception if they are absent
this command invokes the build_runner in dev / release mode respectively:
$ flutter pub run build_runner build
$ flutter pub run build_runner build --release
the builder looks roughly as follows (saved in lib/devdata/devdata.dart):
import 'dart:async';
import 'dart:io';
import 'package:build/build.dart';
Builder devData(BuilderOptions options) => DevDataBuilder(options);
class DevDataBuilder implements Builder {
final BuilderOptions options;
DevDataBuilder(this.options);
#override
FutureOr<void> build(BuildStep buildStep) async {
var mode = options.config['mode'];
var inputId = buildStep.inputId;
var sourcePath = inputId.path;
var targetPath = sourcePath.replaceFirst('lib/devdata/', 'assets/devdata/');
final outputId = AssetId(
inputId.package,
targetPath,
);
if (mode == 'dev') {
log.info('copying $sourcePath to $targetPath');
var contents = await buildStep.readAsString(inputId);
await buildStep.writeAsString(outputId, contents);
} else {
var file = File(targetPath);
if (await file.exists()) {
log.info('deleting $targetPath');
file.delete();
}
}
}
#override
Map<String, List<String>> get buildExtensions {
return {
'lib/devdata/{{file}}.json': const ['assets/devdata/{{file}}.json'],
};
}
}
the corresponding build.yaml is here:
builders:
test_data:
import: 'package:my_app_package/builder/devdata.dart'
builder_factories: ['devData']
build_extensions: {"lib/devdata/{{file}}.json": ["assets/testdata/{{file}}.json"]}
build_to: source
auto_apply: root_package
defaults:
generate_for:
include:
- lib/devdata/**
global_options:
my_app_package:test_data:
options:
mode: prod
dev_options:
mode: dev
here are some additional resources that led to this approach:
https://pub.dev/packages/build
https://pub.dev/packages/build_config
https://pub.dev/packages/build_runner
https://github.com/dart-lang/build/tree/master/example
https://github.com/dart-lang/build/blob/master/docs/build_yaml_format.md
https://pedromarquez.dev/blog/2022/8/flutter-code-gen-1
https://pedromarquez.dev/blog/2022/9/flutter-code-gen-2
https://github.com/flutter/flutter/issues/5813
https://github.com/flutter/flutter/pull/25487
an alternative approach without a builder can be found here (using the pr #25487 mentioned above):
https://github.com/sestegra/flutter_assets_flavors