I am having trouble with my Flutter web app when I tried to publish it because it cannot be loaded on mobile devices, even if they are set up to show the computer version.
@override
void paint(Canvas canvas, Size size) {
var center = Offset((size.width / 2), (size.height / 2));
var roundedRectangle = RRect.fromRectAndRadius(
Rect.fromCenter(center: center, width: size.width, height: size.height),
Radius.circular(borderRadius),
);
// Visible line of the shadow
Paint line = Paint()
..color = color
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke
..strokeWidth = circleWidth
..maskFilter = MaskFilter.blur(BlurStyle.normal, blurSigma);
// draw on canvas
canvas.saveLayer(Rect.largest, Paint());
Paint line2 = Paint()..blendMode = BlendMode.clear;
canvas.drawRRect(roundedRectangle, line);
canvas.drawRRect(roundedRectangle, line2);
canvas.restore();
}
When I first included this code, the BlendMode.clear
gave an incompatibility error for mobile, so I had to add --dart-define=FLUTTER_WEB_USE_SKIA=true
to the running arguments. I thought this solved the problem, but it still does not work after being launched. Is there any other library with an effect similar to BlendMode.clear that can be rendered by mobile devices, or any way to fix surpass this problem?
EDIT:
So I discovered the problem: mobile devices use HTML renderer by default and I can only use Canvaskit with this animation. So I should enforce Canvaskit to render my web. There are two ways to do this, first one is at build time. I don't know if this would work once the website is published and devices access it through the host name? There is also the factor that we are using CI/CD for deployment, and I don't know if we can set this.
Second option is to enforce it at runtime, which is causing me problems. For that I should edit my public/index.html
. The problem here is that I don't have a default index. There is a service worker activated, and if I remove it, the web doesn't run, it just shows a white screen. I managed to combine the initializationEngine code and the service worker, but it only run after a hot reload (so it only showed a white screen if accessed through the link).
This is how to initialize the canvaskit in runtime:
<html>
<head>
<script src="flutter.js" defer></script>
</head>
<body>
<script>
window.addEventListener('load', function (ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: async function(engineInitializer) {
// Initialize the Flutter engine
let appRunner = await engineInitializer.initializeEngine();
// Run the app
await appRunner.runApp();
}
});
});
</script>
</body>
</html>
This is my current index.html
<html>
<head>
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="website">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>Apphy</title>
<link rel="manifest" href="manifest.json">
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD3d1bOcWTEPPFZIeCqofEv2ipL23bsJTU"></script>
</head>
<body>
<!-- added for q_form package -->
<script src="assets/packages/libphonenumber_plugin/js/libphonenumber.js"></script>
<script src="assets/packages/libphonenumber_plugin/js/stringbuffer.js"></script>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
var serviceWorkerVersion = null;
var scriptLoaded = false;
function loadMainDartJs() {
if (scriptLoaded) {
return;
}
scriptLoaded = true;
var scriptTag = document.createElement('script');
scriptTag.src = 'main.dart.js';
scriptTag.type = 'application/javascript';
document.body.append(scriptTag);
}
if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl)
.then((reg) => {
function waitForActivation(serviceWorker) {
serviceWorker.addEventListener('statechange', () => {
if (serviceWorker.state == 'activated') {
console.log('Installed new service worker.');
loadMainDartJs();
}
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing || reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log('New service worker available.');
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log('Loading app from service worker.');
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
'Failed to load app from service worker. Falling back to plain <script> tag.',
);
loadMainDartJs();
}
}, 4000);
});
} else {
// Service workers not supported. Just drop the <script> tag.
loadMainDartJs();
}
</script>
</body>
</html>
And this is how I combined them, but it only worked in debugging after a hot reload and didn't work at all if accessed through link. There is a log in the Chrome console with this index.html while the screen is white: 'Installed new service worker.'. But it never starts again.
<!DOCTYPE html>
<html>
<head>
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="Apphy" content="App development that makes you happy.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="website">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>Apphy</title>
<link rel="manifest" href="manifest.json">
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD3d1bOcWTEPPFZIeCqofEv2ipL23bsJTU"></script>
<script src="flutter.js" defer></script>
</head>
<body>
<!-- added for q_form package -->
<script src="assets/packages/libphonenumber_plugin/js/libphonenumber.js"></script>
<script src="assets/packages/libphonenumber_plugin/js/stringbuffer.js"></script>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
var serviceWorkerVersion = null;
var scriptLoaded = false;
function loadMainDartJs() {
if (scriptLoaded) {
return;
}
scriptLoaded = true;
window.addEventListener('load', function(ev) {
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: async function(engineInitializer) {
// Configure the engine options
let engineOptions = new _flutter.JsFlutterConfiguration();
engineOptions.renderer = "canvaskit";
// Initialize the Flutter engine
let appRunner = await engineInitializer.initializeEngine(engineOptions);
//let appRunner = await engineInitializer.initializeEngine();
// Run the app
await appRunner.runApp();
}
});
});
}
if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl)
.then((reg) => {
function waitForActivation(serviceWorker) {
serviceWorker.addEventListener('statechange', () => {
if (serviceWorker.state == 'activated') {
console.log('Installed new service worker.');
loadMainDartJs();
}
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing || reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log('New service worker available.');
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log('Loading app from service worker.');
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
'Failed to load app from service worker. Falling back to plain <script> tag.',
);
loadMainDartJs();
}
}, 4000);
});
} else {
// Service workers not supported. Just drop the <script> tag.
loadMainDartJs();
}
</body>
</html>