Let’s get starter!

The first “issue” I had, having a website, is “how to move from one path to the other?”. I used:

Navigator.of(context).push(MaterialPageRoute(builder: (_) => Game()));

Nothing, new… but I needed to update the URL and the first solution came with the amazing dart:html library:

import 'dart:html' as html; @override

void initState() {

html.window.history.pushState("", "Game", "/game");

super.initState();

}

I can even move back and forward through the history with:

html.window.history.back();

html.window.history.forward();

That’s great! Now my Web App looks more like a Website! My current url:

https://gametaphero.web.app/game

But! (there’s always a but…), if you go directly to that URL, the website doesn’t open, you’ll get the terrible 404 screen 😩😩😩

First Solution!

If the websites read the converted web/main.dart into my index.html one… could I have more files? 🤔🤔🤔

I decided to create initgame.dart, web/game.dart and game.html files:

// initgame.dart void main() => runApp(MyApp());



class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MaterialApp(

title: 'Game'),

home: Game(),

);

}

}

The new file to be “transformed” into Js:

// web/game.dart

import 'package:mygame/initgame.dart' as game; main() async {

await ui.webOnlyInitializePlatform();

game.main();

}

And the game.html to point into that one:

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Game On!</title>

<script defer src="game.dart.js" type="application/javascript"></script>

</head>

</html>

Now, we run the code again and voila!

Remember to update the pushState method to have the correct URL:

html.window.history.pushState("", "Game", "/game.html");

You can press: cmd + R or F5 all you want, it works! IT WORKS! 🎉🎊 You can go either from:

http://gametaphero.web.app and click “TAP TO START”

or you can skip all that and play directly from:

https://gametaphero.web.app/game.html

Does what we need and is one step closer to our Flutter Website… but that’s a lot of files! Imagine if we have a /support, and /privacy, and /download, and, and, and 🙃🙃🙃 With just 4 paths we’ll have 12 files ONLY to allow users to move directly to a specific screen. Should be a better way to do it…

Second Solution!

Alright… if we’re reading an html file, it should be a way to read the content of it, right? RIGHT?! (first-time web developer intensifies)

What if we had like a tag or something inside the .html, passing that value as a parameter and then do the logic… like deep linking, you know.

First thing first, let’s delete everything! Wait, not everything… only the initgame.dart and the web/game.dart files.

(Ptssss: never delete files before making sure your new logic actually works, your current logic is always the “best” logic).

We need to make a small change inside our game.html one:

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Game On!</title>

<script id="route">game</script>

<script defer src="main.dart.js" type="application/javascript"></script>

</head>

</html>

I’d added a small text string <script id=”route”>game</script> and now we’re back into our main.dart.js file.

So, if you go into https://gametaphero.web.app/game.html you’ll be redirected to your web/main.dart file, then into your main.dart and being able to read the text inside the new script tag. How to do it, you may ask:

import 'dart:html' as html;

import 'dart:js' as js; // only if you want to do Js logic void main() {

var routePath = "/";



var route = html.window.document.getElementById("route");

if (route != null) {

routePath += route.innerHtml;

}



// js.context.callMethod('alert', <String>[routePath]);

// this method will show an alert over your browser,

// delete it if your logic works. runApp(MyApp(routePath));

} class MyApp extends StatelessWidget {



final String route;



MyApp(this.route); @override

Widget build(BuildContext context) {

return ...;

}

}

Now you can apply a “deep linking” logic inside your main.dart build method:

Widget page() {

switch (route) {

case "/": return Home();

case "/game": return Game();

case "/download": return Download();

// more cases and screens as you need!

default: return Home(); // to be double sure

}

} @override

Widget build(BuildContext context) {

return MaterialApp(

home: page(),

);

}

Save, RUN, cross your fingers, don’t wait (because Flutter builds so fast 🔥) and:

IT WORKS!!! 🎉🎊🔥

We literally saved lots of work with this logic! No need to create multiple dart files, all we need to do is one extra .html file per screen and that’s it! My dream to build a full Website using a “mobile developer logic” is one step closer to reality! ❤️

Best Solution (so far)

Being super excited, I jumped into Flutter Study Group Zoom call (Every Wednesday 24hs or Q&A) and Simon was there. Being a superhuman being of knowledge, he knew a better way to tackle this drama and not even having .html per path.

We need a bit more of work but, after that, new pages will be A,B, C-simple (Tpum tssssss 🥁)

onGenerateRoute

Let’s go back to our main.dart file, we need to make some changes but we ended up with a similar solution as the one before:

void main() => runApp(MyApp());



class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MaterialApp(

onGenerateRoute: (settings) {

switch(settings.name) {

case "/": return Welcome.route();

case "/game": return Game.route();

case "/privacy": return Privacy.route();

case "/support": return Support.route();

case "/download": return Download.route();

default: return Welcome.route();

}

},

initialRoute: "/",

);

}

}

You may ask what the .route(); the method is all about… Is just a static method that builds a route for you. On every StatefulWidget you’ll need to add the following:

static Route<dynamic> route() {

return SimpleRoute(

name: '/',

title: 'Tap Hero',

builder: (_) => Welcome(),

);

}

For my game, will be something like this:

static Route<dynamic> route() {

return SimpleRoute(

name: '/game',

title: 'Game On',

builder: (_) => Game(),

);

}

Those hold a similar logic to our path and title name but adding the Route logic of Flutter.

class SimpleRoute extends PageRoute {

SimpleRoute({

@required String name,

@required this.title,

@required this.builder,

}) : super(settings: RouteSettings(

name: name,

));



final String title;

final WidgetBuilder builder;



@override

Color get barrierColor => null;



@override

String get barrierLabel => null;



@override

bool get maintainState => true;



@override

Duration get transitionDuration => Duration(milliseconds: 0);



@override

Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {

return Title(

title: this.title,

color: Theme.of(context).primaryColor,

child: builder(context),

);

}

}

Pro Tip!

If you want to have an animated Fade Animation, you could do something like this:

class FadeRoute extends PageRoute {

FadeRoute({

@required String name,

@required this.title,

@required this.builder,

}) : super(settings: RouteSettings(

name: name,

));



final String title;

final WidgetBuilder builder;



@override

Color get barrierColor => null;



@override

String get barrierLabel => null;



@override

bool get maintainState => true;



@override

Duration get transitionDuration => Duration(milliseconds: 500);



@override

Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {

return Title(

title: this.title,

color: Theme.of(context).primaryColor,

child: builder(context),

);

}



@override

Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {

return FadeTransition(opacity: animation, child: child);

}

}

You’re all set! Congratulation! You don’t need the .html file anymore, the routes will take that task for you ❤️❤️❤️