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

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
}
}

Related

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.

Remove hash symbol ' # ' from Flutter web navigation

I want to create one simple web application with Flutter web but after I create some simple application with this document I faced with some issue on routing address it automatically add one hash '#' symbol to URL on the address bar, I want to know how I can remove this sign from URL, In fact, right now I see something like this on browser address-bar : http://[::1]:54587/#/register but I want to achieve to something like this http://[::1]:54587/register.
Configuring the URL strategy on the web
Include the flutter_web_plugins package and call the setUrlStrategy function before your app runs:
dependencies:
flutter_web_plugins:
sdk: flutter
Create a lib/configure_nonweb.dart file with the following code:
void configureApp() {
// No-op.
}
Create a lib/configure_web.dart file with the following code:
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
void configureApp() {
setUrlStrategy(PathUrlStrategy());
}
Open lib/main.dart and conditionally import configure_web.dart when the html package is available, or configure_nonweb.dart when it isn’t:
import 'package:flutter/material.dart';
import 'configure_nonweb.dart' if (dart.library.html) 'configure_web.dart';
void main() {
configureApp();
runApp(MyApp());
}
If your only concern is for routing, you can check out my answer here: https://stackoverflow.com/a/63042805/210417
Basically it just splits the current URL into a List and then removes the empty ones caused by the hash tag.

Flutter widget tests with platform branching

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

Spock + GEB vs. Robot Framework [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 3 years ago.
Improve this question
Previously I used Robot Framework to automate testing of applications, but a new client asked pay attention to Spock + GEB. I've never used it, but I need to quickly compare two of these tools and make a choice. Please help me to understand how they differ and what are the strengths / weaknesses of each.
I tell you about Geb, i use gebish for testing web-applications more 6 months. That's all his benefits:
Cross Browser Automation
jQuery-like API
Page Objects
Asynchronicity
Testing
Build System Integration
Now more details about each of them.
Cross Browser Automation
Geb leverages the WebDriver library for browser automation. This means that Geb works with any browser that WebDriver works with, and the list of browsers that WebDriver works with is growing all the time.
The core supported browsers are:
FireFox
Internet Explorer
Google Chrome
Opera
There is also experimental support for:
Chrome on Android
Safari on iPhone & iPad
WebDriver also supports remote drivers. This allows you to automate a browser running on another machine! This means you can easily run your test suite against an IE browser from the comfort of your Mac or Linux machine (and vice versa).
jQuery-like API
Geb takes inspiration from jQuery to provide a concise and effective way to get at content. This is called the Navigator API.
The dollar function can be used anywhere to select content based on CSS selectors, attribute matchers and/or indexes.
// CSS 3 selectors
$("div.some-class p:first[title='something']")
// Find via index and/or attribute matching
$("h1", 2, class: "heading")
$("p", name: "description")
$("ul.things li", 2)
// 'text' is special attribute for the element text content
$("h1", text: "All about Geb")
// Use builtin matchers and regular expressions
$("p", text: contains("Geb"))
$("input", value: ~/\d{3,}-\d{3,}-\d{3,}/)
// Chaining
$("div").find(".b")
$("div").filter(".c").parents()
$("p.c").siblings()
Page Objects
Geb has first class support for the Page Object pattern, leveraging Groovy's DSL capabilities to allow you the developer to easily define the interesting parts of your pages in a concise, maintanable and extensible manner.
import geb.Page
class LoginPage extends Page {
static url = "http://myapp.com/login"
static at = { heading.text() == "Please Login" }
static content = {
heading { $("h1") }
loginForm { $("form.login") }
loginButton(to: AdminPage) { loginForm.login() }
}
}
class AdminPage extends Page {
static at = { heading.text() == "Admin Section" }
static content = {
heading { $("h1") }
}
}
Asynchronicity
Modern web pages are full of asynchronous operations like AJAX requests and animations. Geb provides built in support for this fact of life.
Any content lookup, or operation can be wrapped in a waitFor clause.
waitFor {
$("p.status").text() == "Asynchronous operation complete!"
}
This will keep testing the condition for a certain amount of time (which is configurable) until it passes. The same technique can be used to just wait for the content, not necessarily for the content to have some characteristic.
def dynamicParagraph = waitFor { $("p.dynamically-added") }
dynamicParagraph.text() == "Added dynamically!"
You can also define that content should be implicitly waited for in the Content DSL for page objects
import geb.Page
class DynamicPage extends Page {
static content = {
dynamicParagraph(wait: true) { $("p.dynamically-added") }
}
}
With this definition, when dynamicParagraph is requested Geb will implictly wait for a certain amount of time for it to appear.
Testing
Geb provides integration modules for popular testing frameworks such as Spock, JUnit, TestNG, EasyB and Cucumber (via Cuke4Duke)
While Geb works great with all of these frameworks, it really shines with Spock. Spock is an innovative testing framework that is a great match for using with Geb. Using Spock + Geb gives you very clear, concise and easy to understand test specifications with very little effort.
import geb.Page
import geb.spock.GebSpec
class LoginSpec extends GebSpec {
def "login to admin section"() {
given:
to LoginPage
when:
loginForm.with {
username = "admin"
password = "password"
}
and:
loginButton.click()
then:
at AdminPage
}
}
Build System Integration
Geb is easy to integrate into any build system, and information and examples on integrating with the following build/project systems is available:
Gradle
Grails
Maven
You can look my Example (Spock+GEB) here: github
Read more about geb here: Official Site
Thanks!!!

Eclipse RCP: How to access internal classes of plugins?

I want to use the default XML editor (org.eclipse.wst.xml.ui) of Eclipse in an RCP application. I need to read the DOM of the xml file currently open. The plugin doesn't offer any extension point, so I'm trying to access the internal classes. I am aware that the I should not access the internal classes, but I don't have another option.
My approach is to create a fragment and an extension point to be able to read data from the plugin. I'm trying not to recompile the plugin, that's why I thought that a fragment was necessary. I just want to load it and extract the data at runtime.
So, my question is: is there another way to access the classes of a plugin? if yes, how?
Any tutorial, doc page or useful link for any of the methods is welcome.
Since nobody answered my question and I found the answer after long searches, I will post the answer for others to use if they bump into this problem.
To access a plugin at runtime you must create and extension point and an extension attached to it into the plugin that you are trying to access.
Adding classes to a plugin using a fragment is not recommended if you want to access those classes from outside of the plugin.
So, the best solution for this is to get the plugin source from the CVS Repository and make the modifications directly into the source of the plugin. Add extension points, extensions and the code for functionality.
Tutorials:
Getting the plugin from the CVS Repository:
http://www.eclipse.org/webtools/community/tutorials/DevelopingWTP/DevelopingWTP.html
Creating extensions and extension points and accessing them:
http://www.vogella.de/articles/EclipseExtensionPoint/article.html
http://www.eclipsezone.com/eclipse/forums/t97608.rhtml
I ended up extending XMLMultiPageEditorPart like this:
public class MultiPageEditor extends XMLMultiPageEditorPart implements
IResourceChangeListener {
#Override
public void resourceChanged(IResourceChangeEvent event) {
// TODO Auto-generated method stub
setActivePage(3);
}
public Document getDOM() {
int activePageIndex = getActivePage();
setActivePage(1);
StructuredTextEditor fTextEditor = (StructuredTextEditor) getSelectedPage();
IDocument document = fTextEditor.getDocumentProvider().getDocument(
fTextEditor.getEditorInput());
IStructuredModel model = StructuredModelManager.getModelManager()
.getExistingModelForRead(document);
Document modelDocument = null;
try {
if (model instanceof IDOMModel) {
// cast the structured model to a DOM Model
modelDocument = (Document) (((IDOMModel) model).getDocument());
}
} finally {
if (model != null) {
model.releaseFromRead();
}
}
setActivePage(activePageIndex);
return modelDocument;
}
}
This is not a clean implementation, but it gets the job done.