Flutter widget tests with platform branching - flutter

In my widget's code I need to branch on Android/iOS to show widgets specific to each platform, and also call platform specific APIs (e.g. the Android widget will call an API only available on Android).
if (Platform.isAndroid) {
return WidgetAndroid(...);
} else if (Platform.isIOS) {
return WidgetIOS(...);
}
How can I test that the right widget is shown using a Flutter widget test?
I know I can check that the widget exists but how do I run the test with a specific platform.
expect(find.byType(WidgetIOS), findsOneWidget);

In 2022 it is not recommended to use dart:io and Platform class to determine the runtime platform. The better alternative is to use defaultTargetPlatform available through 'package:flutter/foundation.dart'. I.e.:
if (defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS) {}
Should you have that approach for determining platform in your code overiding platform in tests is as easy as setting debugDefaultTargetPlatformOverride in your test body.
https://api.flutter.dev/flutter/foundation/debugDefaultTargetPlatformOverride.html
E.g.:
testWidgets('DataTable2 renders with DataRow.index()',
(WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
...
debugDefaultTargetPlatformOverride = null;
});

Both Platform.isAndroid and Platform.isIOS return false when running testWidgets.
Some suggestions we can apply in our tests:
Pass the platform as variable to your top level Widget, that way you will be able to provide that value manually.
Make the case you want to test an else without if, so that will run during the tests.
Use instead Platform from https://pub.dev/packages/platform which allows mocking, which is probably the best long term solution.

Platform is in dart:io, which is lower-level than Flutter's TargetPlatform. In most cases, you should use the latter.
From documentation:
The [dart:io.Platform] object should only be used directly when it's critical to actually know the current platform, without any overrides possible (for example, when a system API is about to be called).

Like Miguel mentioned you can't mock it directly.
If you use the provider framework, you can use dependency injection in your tests to make this happen without exposing an additional param in your widget.
Create an class to use for injection:
class PlatformInfo {
final bool isIos;
final bool isAndroid;
PlatformInfo({#required this.isAndroid,#required this.isIos});
}
in your top level MultiProvider widget, set up your provider bindings:
Provider<PlatformInfo>.value(
value: PlatformInfo(isAndroid: Platform.isAndroid, isIos: Platform.isIOS),
),
Use it in your widget:
Provider.of<PlatformInfo>(context).isIos

Related

How to update data in flutter

I use mvvm architecture and I want to update my data in the view model without the need to call rebuild methods, use stream or future builders, and use any external packages. Overall I want to listen to changes not only on a specific variable but on the entire class instead
I tried using inherited widget but this doesn't help
for that, you should any os flutter state manager. You can use Bloc but for MVVM architecture you can use Getx. Try to learn Getx .obs and GetxBuilder to listen to new changes and update your application according to them.
Package : https://pub.dev/packages/get
you can use Provider as a state management in your project to make all the data that you need alive in your project:
step 1) please add provider: ^6.0.5 (or any version that is compatible) in your pubspec.yaml and call flutter pub get.
step 2) now you should create a provider class to make all the variables that you want to use everywhere, alive. please create a dart file as below:
import 'package:flutter/material.dart';
class ExpampleClassProvider extends ChangeNotifier {
String? _yourData;
void setYourData(String? newData){
_yourData = newData;
notifyListeners();
}
String? get yourData => _yourData;
}
as you see when _yourData is changed, it tells you and you can use this data where ever you want by providing ExpampleClass,even you can set a data in your first screen and use that data in the last screen without passing data step page by step.
step 3) you should add the provider to main.dart:
MultiProvider(
providers: [
// you are adding your provider
ListenableProvider.value(value: ExpampleClassProvider()),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: ...........
),
);
now you can use it where ever you want:
Provider.of<ExpampleClass>(context, listen: false).yourData;
and even you can use that data in your widgets like this by using Consumer everywhere you want:
Consumer<ExpampleClassProvider>(
builder: (context, exampleClassProvider ,snapshot) {
return Text(exampleClassProvider!.yourData);
}
)
read the Provider document carefully.
happy coding...

How to structure BLoCs for screens with cross-dependent entities

I am currently trying to learn the BLoC pattern and still struggle with figuring out the best architecture for an app where entities in the model have cross-dependencies that also have implications on screens.
I am building a very simple CRUD Flutter app that allows the user to manage and tag a movie database. The database is implemented using the SQlite plugin and Drift. So far, there are 4 main screens:
MoviesListScreen
MovieDetailsScreen
TagsListScreen
TagDetailsScreen
So obviously, the list screens list all movies / tags in the database, respectively, and allow you to add and delete entities. When you click on an entitiy on a list screen, you reach the details screen with all information on the respective movie / tag.
From what I have read, it is recommended to have one bloc per feature or screen. Following this tutorial, I created two blocs, one for movies and one for tags. Here is the tags state:
enum TagsStatus { initial, success, error, loading, selected }
extension TagsStatusX on TagsStatus {
bool get isInitial => this == TagsStatus.initial;
bool get isSuccess => this == TagsStatus.success;
bool get isError => this == TagsStatus.error;
bool get isLoading => this == TagsStatus.loading;
bool get isSelected => this == TagsStatus.selected;
}
class TagsState extends Equatable {
final TagsStatus status;
final List<Tag> tags;
final Tag selectedTag;
final List<Movie> moviesWithSelectedTag;
const TagsState(
{this.status = TagsStatus.initial, List<Tag> tags, Tag selectedTag, List<Movie> moviesWithSelectedTag})
: tags = tags ?? const [],
selectedTag = selectedTag,
moviesWithSelectedTag = moviesWithSelectedTag;
#override
List<Object> get props => [status, tags, selectedTag];
TagsState copyWith({TagsStatus status, List<Tag> tags, Tag selectedTag, List<Movie> moviesWithSelectedTag}) {
return TagsState(
status: status ?? this.status,
tags: tags ?? this.tags,
selectedTag: selectedTag ?? this.selectedTag,
moviesWithSelectedTag: moviesWithSelectedTag ?? this.moviesWithSelectedTag);
}
}
And the corresponding bloc:
class TagsBloc extends Bloc<TagEvent, TagsState> {
final Repository repository;
TagsBloc({this.repository}) : super(const TagsState()) {
on<GetTags>(_mapGetTagsEventToState);
on<SelectTag>(_mapSelectTagEventToState);
}
void _mapGetTagsEventToState(GetTags event, Emitter<TagsState> emit) async {
emit(state.copyWith(status: TagsStatus.loading));
try {
final tags = await repository.getTags();
emit(
state.copyWith(
status: TagsStatus.success,
tags: tags,
),
);
} catch (error, stacktrace) {
print(stacktrace);
emit(state.copyWith(status: TagsStatus.error));
}
}
void _mapSelectTagEventToState(event, Emitter<TagsState> emit) async {
emit(
state.copyWith(
status: TagsStatus.selected,
selectedTag: event.selectedTag,
),
);
}
}
Now this works perfectly fine to manage the loading of the list screens. (Remark: I could create separate blocs for the details screens, because it feels a bit out of place to have the selectedTag bloc in the state that is used for the TagsListScreen, even though the selected tag will be displayed on a different screen. However, that would create additional boilerplate code, so I am unsure about it.)
What I really struggle with is how to access all tags in the MovieDetailsScreen where I am using the MoviesBloc. I need them there as well in order to display chips with tags that the user can add to the selected movie simply by clicking on them.
I thought about the following possibilities:
Add all tags to the MoviesBloc - that would go against the point of having two separate blocs and I would have to make sure that both blocs stay in sync; moreover, a failure loading the tags would also cause a failure loading the movies, even in widgets that don't even use the tags
Subscribing to the TagsBloc in MoviesBloc - seems error-prone to me, also same as in point 1
Creating separate blocs for the list screens and details screens - lots of redundancy and additional boilerplate code
Nesting two BlocBuilder components in the MovieDetailsScreen - BlocBuilder currently does not support more than one bloc, probably because this is an anti-pattern
Using one single bloc that holds movies as well as tags and use it in all 4 screens - discouraged by the creators of the BLoC package; also I would need two status properties to manage the loading from the database separately for movies and tags, which I feel should be in separate blocs
What would be the recommended way to handle this kind of business logic with blocs?
Option 4
As you're no dought aware, there are no correct and incorrect answers here, it's a matter of design. And if you ask me, I wouldn't consider movies and tags as two separate features, but that highly depends on the project domain and one's definition of a feature, so let's not go there.
Answering your question I'd go with option 4. Nesting BlocBuilders is a quite common pattern in the bloc architecture, I've seen it many times.
Also, in the same thread you've referenced, the author is recommending that idea, so I don't think it's an anti-pattern.
p.s. option 3 is also fine, and maybe you can avoid the redundancy by creating a class that contains the shared logic between the two cubits, thus, you can still maintain them together, while having the perks of two separate blocs.
There is a recommendation from the author of flutter_bloc available on the flutter_bloc documentation page.
https://bloclibrary.dev/#/architecture?id=bloc-to-bloc-communication
Some key-lines from the page:
...it may be tempting to make a bloc which listens to another bloc.
You should not do this.
...no bloc should know about any other bloc.
A bloc should only receive information through events and from injected repositories
So in short, something like your options 3 or 4. Perhaps with a touch of a BlocListener there to trigger events between the blocs.
There is no problem having multiple blocs per screen. Consider having a bloc controlling the state of a button based on some API calls or a Stream of data, while other parts of the screen are determined by another bloc.
It is not a bad thing to have an "outer" bloc determining if a part of a screen should be visible, but that inner part's state is handled by a separate bloc.
Subscribing to the TagsBloc in MoviesBloc - seems error-prone to me, also same as in point 1
This is the cleanest approach. I have been using it and it scales pretty well as number of blocs increases.

How to ignore package when building flutter project for web?

I have a project which uses flutter_libserialport library on macOS.
I am modifying it to work on web however this library does not work on web.
I am building a web implementation using navigator.serial in javascript which works fine.
However when I attempt to build the project for web I get the following error
/opt/homebrew/Caskroom/flutter/2.2.3/flutter/.pub-cache/hosted/pub.dartlang.org/libserialport-0.2.0+3/lib/src/config.dart:25:8: Error: Not found: 'dart:ffi'
import 'dart:ffi' as ffi;
This makes sense since FFI is not available on web.
But I don't even need the libserialport library on web any way.
How can I get flutter to ignore it?
I tried this however it doesn't contain information on how to exclude a package.
It also does not contain information on how to ignore it specifically for web. It seems to just ignore it in general.
Maybe you should guard your usages of libserialport with the kIsWeb predicate like following:
if(!kIsWeb){
// libserialport code execution here
}
I searched a lot as well and didn't find a way you can do that, I think this should be handled by the package itself not the package's users like in path_provider for instance.
As a workaround I have created a dummy libserialport's SerialPort class for web only as follows:
dummy_serialport.dart:
class SerialPort {
final String name;
static List<String> availablePorts = ['dummy'];
static SerialPortError? lastError;
SerialPort(this.name);
bool openReadWrite() {
return false;
}
}
class SerialPortError {}
// add more properties and functions as needed
main.dart:
import 'package:libserialport/libserialport.dart'
if (dart.library.html) './dummy_serialport.dart'
if (dart.library.io) 'package:libserialport/libserialport.dart';
....
if (!kIsWeb) {
final name = SerialPort.availablePorts.first;
final port = SerialPort(name);
if (!port.openReadWrite()) {
print(SerialPort.lastError);
exit(-1);
}
}
....
....
It's bad, I know :( but it works! maybe you can contact the package author to get more insight and if opening a PR where the interfaces are separated from the FFI implementation so that importing the classes wouldn't break web or something.

Flutter Provider best practice

I have been experimenting with the provider package and am usually able to get it to do what I want it to do. However, in some cases I am not sure if what I am doing is at all best practice.
For example, suppose I have a settings page with various, unrelated options - say a theming option, a notifications option, some filter options specific to the app etc.
My question is, should each of these options have their own class dedicated to a single value so that only the parts of the widget tree dependent on that single value rebuild. Or should they all be in the same SettingsProvider class, and there is some way of using the fields in this class separately so as to avoid excessive rebuilds?
Or am I missing the bigger picture entirely? Any help would be great thanks!
A solution I've found is to put all the values in a single class eg SettingsProvider. Then, instead of using Provider.of<> or Consumer<>,
use Selector<>. For example, to get/set the notifications option of settings, you could wrap the widget with a Selector like so -
Selector<SettingsProvider, bool>(
builder: (context, notifications, child) {
(notifications)
? return Text('Notifications are on')
: return Text('Notifications are off')
},
selector: (context , settingsPro) => settingsPro.notifications,
),
This should display whether or not the notifications are on, and is only rebuilt when the notification option changes.
Here is the provider doc page
Here is an article about Selector
Let me know if there are any better solutions.

Flutter - How use conditional compilation for platform (Android, iOS, Web)?

I am creating a mobile app in Flutter. Now I have a problem, for one platform I will use a plugin for another, I need to write my platform code (the implementation of the plugin is not suitable).
I see several solutions:
It would be optimal to create several projects and use conditional compilation and shared files in them. I used this technique in visual studio. but I am now using android studio. there is no project file, only folders.
Also a problem with conditional compilation support. I found this article and conditional compilation is very limited.
create your own plugin and use it fully. but it is more labor intensive.
What do you advise maybe there is a third way?
When working with multiple environments (eg. IO and Web) it might be useful to add stub classes to resolve dependencies at compile time, this way, you can easily integrate multiple platform dependent libraries, without compromising compiling for each of it.
For example, one can have have a plugin structured in the following way:
- my_plugin_io.dart
- my_plugin_web.dart
- my_plugin_stub.dart
- my_plugin.dart
Let's break it down, with a simple example:
my_plugin.dart
This is where you can actually have your plugin's class to be used across multiple projects (ie. environments).
import 'my_plugin_stub.dart'
if (dart.library.io) 'my_plugin_io.dart'
if (dart.library.html) 'my_plugin_web.dart';
class MyPlugin {
void foo() {
var bar = myPluginMethod(); // it will either resolve for the web or io implementation at compile time
}
}
my_plugin_stub.dart
This is what will actually resolve at compile time (stubbing) to the right myPluginMethod() method.
Object myPluginMethod() {
throw UnimplementedError('Unsupported');
}
And then create the platform implementations
my_plugin_web.dart
import 'dart:html' as html;
Object myPluginMethod() {
// Something that use dart:html data for example
}
my_plugin_io.dart
import 'dart:io';
Object myPluginMethod() {
// Something that use dart:io data for example
}
Other official alternatives may pass from creating separated projects that share the same interface. This is just like Flutter team has been doing for their web + io plugins, resulting in a project that can be bundled with multiple projects:
- my_plugin_io
- my_plugin_web
- my_plugin_desktop
- my_plugin_interface
A good article explaining this may be found here.
Just typed it right here in SO, so I'm sorry if I had some typo, but you should easily find it on an editor.
You just need to import:
import 'dart:io';
And then use conditionals based on:
// Platform.isIOS // Returns true on iOS devices
// Platform.isAndroid // Returns true on Android devices
if (Platform.isIOS) {
navigationBar = new BottomNavigationBar(...);
}
if (Platform.isAndroid) {
drawer = new Drawer(...);
}
Add this library (no package needed)
import 'dart:io' show Platform;
Now you can create a function that checks which platform the user is using.
Widget getWidgetBasedOnPlatform() {
if (Platform.isIOS) {
return Container(); //the one for iOS
}
else if (Platform.isAndroid) {
return Container(); //the one for Android
}
}