SlideShare a Scribd company logo
1 of 134
Download to read offline
VOYAGER FOR FLUTTER
REQUIREMENTS AS CODE
DEPENDENCY INJECTION
THE WIDGET ROUTER
ŁUKASZ WIŚNIEWSKI
@VISHNA
_SAMPLE_APP
#ISSUE 1Home
Hello World!
Home Screen:
•Use HomeWidget
•Title “Home”
•Body “Hello World”
•FAB with “plus” icon
•FAB goes to the detail screen
#ISSUE 2Icon: plus
Detail Screen:
•Use Detail Widget
•Title “Icon {name}”
•Body is a large Icon
REQUIREMENTSHome Screen:
•Use HomeWidget
•Title “Home”
•Body “Hello World”
•FAB with “plus” icon
•FAB target is detail screen
Details Screen:
•Use DetailWidget
•Title “Icon {name}”
•Body is a large icon
REQUIREMENTS.YAML'/home':
widget: HomeWidget
title: Home
body: "Hello World"
icon: plus
target: "/details/plus"
'/details/:name'
widget: DetailWidget
title: "Icon %{name}"
icon: "%{name}"
'/home':
widget: HomeWidget
title: Home
body: "Hello World"
icon: plus
target: "/details/plus"
'/details/:name'
widget: DetailWidget
title: "Icon %{name}"
icon: "%{name}"
PATH
'/home':
widget: HomeWidget
title: Home
body: "Hello World"
icon: plus
target: "/details/plus"
'/details/:name'
widget: DetailWidget
title: "Icon %{name}"
icon: "%{name}"
PATH
PLUGIN NODE
'/home':
widget: HomeWidget
title: Home
body: "Hello World"
icon: plus
target: "/details/plus"
'/details/:name'
widget: DetailWidget
title: "Icon %{name}"
icon: "%{name}"
PATH
PLUGIN NODE
PLUGIN CONFIG
'/home':
widget: HomeWidget
title: Home
body: "Hello World"
icon: plus
target: "/details/plus"
'/details/:name'
widget: DetailWidget
title: "Icon %{name}"
icon: "%{name}"
PATH
PLUGIN NODE
PLUGIN CONFIG
PARAMETER INTERPOLATION
REQUIREMENTS
CONFIGURATION NAVIGATION
ROUTER
MAP
(YAML)
PATH
PLUGIN icon
VOYAGER
icon
Config
(“plus”)
title
RouterContext
icon
title
Icon
(+)
Config
(“Home”)
ARCHITECTURE
CONFIG
body
widget
target
PLUGIN
class IconPlugin extends RouterObjectPlugin
PLUGIN
class IconPlugin extends RouterObjectPlugin<Icon> {
}
PLUGIN
class IconPlugin extends RouterObjectPlugin<Icon> {
IconPlugin() : super("icon");
}
PLUGIN
class IconPlugin extends RouterObjectPlugin<Icon> {
IconPlugin() : super("icon");
@override
Icon buildObject(
RouterContext context,
dynamic config)
}
PLUGIN
class IconPlugin extends RouterObjectPlugin<Icon> {
IconPlugin() : super("icon");
@override
Icon buildObject(
RouterContext context,
dynamic config) =>
IconHelper.fromName(config.toString());
}
PLUGIN
BOILERPLATE
final paths =
loadPathsFromAssets("assets/navigation.yml");
final plugins = <RouterPlugin>[
WidgetPluginBuilder()
.add<HomeWidget>((context) => HomeWidget())
.add<DetailWidget>((context) => DetailWidget())
.build(),
IconPlugin()
];
Future<Router> router = loadRouter(paths, plugins);
final paths =
loadPathsFromAssets("assets/navigation.yml");
final plugins = <RouterPlugin>[
WidgetPluginBuilder()
.add<HomeWidget>((context) => HomeWidget())
.add<DetailWidget>((context) => DetailWidget())
.build(),
IconPlugin()
];
Future<Router> router = loadRouter(paths, plugins);
final paths =
loadPathsFromAssets("assets/navigation.yml");
final plugins = <RouterPlugin>[
WidgetPluginBuilder()
.add<HomeWidget>((context) => HomeWidget())
.add<DetailWidget>((context) => DetailWidget())
.build(),
IconPlugin()
];
Future<Router> router = loadRouter(paths, plugins);
final paths =
loadPathsFromAssets("assets/navigation.yml");
final plugins = <RouterPlugin>[
WidgetPluginBuilder()
.add<HomeWidget>((context) => HomeWidget())
.add<DetailWidget>((context) => DetailWidget())
.build(),
IconPlugin()
];
Future<Router> router = loadRouter(paths, plugins);
final paths =
loadPathsFromAssets("assets/navigation.yml");
final plugins = <RouterPlugin>[
WidgetPluginBuilder()
.add<HomeWidget>((context) => HomeWidget())
.add<DetailWidget>((context) => DetailWidget())
.build(),
IconPlugin()
];
Future<Router> router = loadRouter(paths, plugins);
final paths =
loadPathsFromAssets("assets/navigation.yml");
final plugins = <RouterPlugin>[
WidgetPluginBuilder()
.add<HomeWidget>((context) => HomeWidget())
.add<DetailWidget>((context) => DetailWidget())
.build(),
IconPlugin()
];
Future<Router> router = loadRouter(paths, plugins);
VoyagerWidget(path: "/home", router: router);
WIDGETS
VoyagerWidget(path: "/home");
WIDGETS
APP
Provider<Router>.value(
value: router,
child: MaterialApp(
home: VoyagerWidget(path: "/home"),
onGenerateRoute: router.generator()
)
);
VoyagerWidget(path: "/home");
APP
Provider<Router>.value(
value: router,
child: MaterialApp(
home: VoyagerWidget(path: "/home"),
onGenerateRoute: router.generator()
)
);
APP
Provider<Router>.value(
value: router,
child: MaterialApp(
home: VoyagerWidget(path: "/home"),
onGenerateRoute: router.generator()
)
);
APP
Provider<Router>.value(
value: router,
child: MaterialApp(
home: VoyagerWidget(path: "/home"),
onGenerateRoute: router.generator()
)
);
APP
Provider<Router>.value(
value: router,
child: MaterialApp(
home: VoyagerWidget(path: "/home"),
onGenerateRoute: router.generator()
)
);
NAVIGATION
Navigator
  .of(context)
  .pushNamed(“/details/plus");
INJECT
VoyagerWidget
HomeWidget
PATH CONFIGVoyager
class HomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final voyager = Provider.of<Voyager>(context);
return Scaffold(
appBar: AppBar(
title: Text(voyager["title"]),
INJECT
class HomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final voyager = Provider.of<Voyager>(context);
return Scaffold(
appBar: AppBar(
title: Text(voyager["title"]),
INJECT
class HomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final voyager = Provider.of<Voyager>(context);
return Scaffold(
appBar: AppBar(
title: Text(voyager["title"]),
),
body: Text(voyager["body"]),
floatingActionButton: FloatingActionButton(
onPressed: () {
INJECT
return Scaffold(
appBar: AppBar(
title: Text(voyager["title"]),
),
body: Text(voyager["body"]),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator
.of(context)
.pushNamed(voyager["target"]);
},
child: voyager["icon"],
)
)
INJECT
body: Text(voyager["body"]),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator
.of(context)
.pushNamed(voyager["target"]);
},
child: voyager["icon"],
)
)
}
}
INJECT
_CODE_GENERATION
REQUIREMENTS.YAML'/home':
widget: HomeWidget
title: Home
body: "Hello World"
icon: plus
target: "/details/plus"
'/details/:name'
widget: DetailWidget
title: "Icon %{name}"
icon: "%{name}"
REQUIREMENTS.YAML
'/home':
type: home
widget: HomeWidget
title: Home
body: "Hello World"
icon: plus
target: "/details/plus"
'/details/:name'
type: details
widget: DetailWidget
title: "Icon %{name}"
icon: "%{name}"
>_ flutter packages pub run voyager:codegen
_CODE_GENERATION
>_
>_ flutter packages pub run voyager:codegen
_CODE_GENERATION
>_ flutter packages pub run voyager:codegen --run-once
_CODE_GENERATION
>_ flutter packages pub run voyager:codegen --run-once
_CODE_GENERATION
🚨 voyager-codegen.yaml not found, creating one for you...
💾 voyager-codegen.yaml created, please edit it
⚙ voyager-codegen.yaml loaded
VOYAGER-CODEGEN.YAML
- name: Voyager
source: assets/navigation.yaml
target: # TODO
- name: Voyager
source: assets/navigation.yaml
target: lib/gen/voyager_gen.dart
VOYAGER-CODEGEN.YAML
- name: Voyager
source: lib/navigation.dart
target: lib/gen/voyager_gen.dart
VOYAGER-CODEGEN.YAML
const String pathHome = "/home";
const String typeHome = "home";
String pathDetail(String name) {
return "/other/$name";
}
const String typeDetail = “detail";
VOYAGER_GEN.DART
VoyagerWidget(path: "/detail/plus");
STRONG TYPED PATHS
VoyagerWidget(path: pathDetail("plus"));
STRONG TYPED PATHS
TESTS
WIDGET
UNIT
E2E
TESTS
WIDGET
UNIT
E2E
- name: Voyager
source: assets/navigation.yaml
target: lib/gen/voyager_gen.dart
WIDGET TESTS
- name: Voyager
source: assets/navigation.yaml
target: lib/gen/voyager_gen.dart
testTarget: test/gen/test_scenarios.dart
WIDGET TESTS
abstract class VoyagerTestScenarios {
const VoyagerTestScenarios(this.defaultWrapper);
final WidgetWrapper defaultWrapper;
List<VoyagerTestHomeScenario> homeScenarios();
List<VoyagerTestDetailScenario> detailScenarios();
}
TEST SCENARIOS
abstract class VoyagerTestScenarios {
const VoyagerTestScenarios(this.defaultWrapper);
final WidgetWrapper defaultWrapper;
List<VoyagerTestHomeScenario> homeScenarios();
List<VoyagerTestDetailScenario> detailScenarios();
}
TEST SCENARIOS
abstract class
VoyagerTestDetailScenario.write(“plus”,
(WidgetTester tester) async {
expect(find.byType(Icon), findsOneWidget);
});
WRITE SCENARIO
void main() {
final router = loadRouter(paths(), plugins());
voyagerAutomatedTests("voyager auto tests”,
router, MyScenarios());
}
EXECUTE SCENARIO
CODE COVERAGE
0%
CODE COVERAGE
70%
- name: Voyager
source: assets/navigation.yaml
target: lib/gen/voyager_gen.dart
testTarget: test/gen/voyager_test_scenarios.dart
SCHEMA VALIDATION
- name: Voyager
source: assets/navigation.yaml
target: lib/gen/voyager_gen.dart
testTarget: test/gen/voyager_test_scenarios.dart
schema:
icon:
output: Icon
import: "package:flutter/widgets.dart"
input:
SCHEMA VALIDATION
- name: Voyager
source: assets/navigation.yaml
target: lib/gen/voyager_gen.dart
testTarget: test/gen/voyager_test_scenarios.dart
schema:
icon:
output: Icon
import: "package:flutter/widgets.dart"
input:
type: string
enum: [plus, heart, minus]
widget:
SCHEMA VALIDATION
source: assets/navigation.yaml
target: lib/gen/voyager_gen.dart
testTarget: test/gen/voyager_test_scenarios.dart
schema:
icon:
output: Icon
import: "package:flutter/widgets.dart"
input:
type: string
enum: [plus, heart, minus]
widget:
input:
type: string
SCHEMA VALIDATION
- name: Voyager
source: assets/navigation.yaml
target: lib/gen/voyager_gen.dart
testTarget: test/gen/voyager_test_scenarios.dart
schema:
icon:
output: Icon
import: "package:flutter/widgets.dart"
input:
type: string
enum: [plus, heart, minus]
widget:
SCHEMA VALIDATION
- name: Voyager
source: assets/navigation.yaml
target: lib/gen/voyager_gen.dart
testTarget: test/gen/voyager_test_scenarios.dart
schema:
icon:
output: Icon
import: "package:flutter/widgets.dart"
input:
type: string
enum: [plus, heart, minus]
widget:
SCHEMA VALIDATION
type: string
enum: [plus, heart, minus]
widget:
input:
type: string
title:
output: String
input:
type: string
body:
output: String
input:
type: string
target:
SCHEMA VALIDATION
type: string
title:
output: String
input:
type: string
body:
output: String
input:
type: string
target:
output: String
input:
type: string
SCHEMA VALIDATION
>_ flutter packages pub run voyager:codegen --run-once
_CODE_GENERATION
✅ Schema validated properly
>_ flutter packages pub run voyager:codegen --run-once
_CODE_GENERATION
🚨 /home@icon: #/icon: star is not a valid enum value
class HomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final voyager = Provider.of<Voyager>(context);
return Scaffold(
appBar: AppBar(
title: Text(voyager["title"]),
INJECT
class HomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final voyager = VoyagerProvider.of(context);
return Scaffold(
appBar: AppBar(
title: Text(voyager["title"]),
INJECT
@override
Widget build(BuildContext context) {
final voyager = VoyagerProvider.of(context);
return Scaffold(
appBar: AppBar(
title: Text(voyager["title"]),
),
body: Text(voyager["body"]),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator
.of(context)
INJECT
@override
Widget build(BuildContext context) {
final voyager = VoyagerProvider.of(context);
return Scaffold(
appBar: AppBar(
title: Text(voyager.title),
),
body: Text(voyager.body),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator
.of(context)
INJECT
),
body: Text(voyager.body),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator
.of(context)
.pushNamed(voyager["target"]);
},
child: voyager["icon"],
)
)
}
}
INJECT
),
body: Text(voyager.body),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator
.of(context)
.pushNamed(voyager.target);
},
child: voyager.icon,
)
)
}
}
INJECT
Future<Router> router = loadRouter(paths, plugins);
VOYAGER FACTORY
loadRouter(paths, plugins, voyagerFactory:
voyagerDataFactory);
VOYAGER FACTORY
- name: Voyager
source: assets/navigation.yaml
target: lib/gen/voyager_gen.dart
testTarget: test/gen/voyager_test_scenarios.dart
schema:
icon:
output: Icon
import: "package:flutter/widgets.dart"
input:
type: string
enum: [plus, heart, minus]
widget:
SCHEMA VALIDATION
PLUGIN STUB
- name: Voyager
source: assets/navigation.yaml
target: lib/gen/voyager_gen.dart
testTarget: test/gen/voyager_test_scenarios.dart
schema:
icon:
pluginStub: true
output: Icon
import: "package:flutter/widgets.dart"
input:
type: string
enum: [plus, heart, minus]
widget:
SCHEMA VALIDATION
PLUGIN STUB
class IconPlugin extends RouterObjectPlugin<Icon> {
IconPlugin() : super("icon");
@override
Icon buildObject(
RouterContext context,
dynamic config) =>
IconHelper.fromName(config.toString());
}
PLUGIN STUB
class IconPlugin extends IconPluginStub {
IconPlugin() : super("icon");
@override
Icon buildObject(
RouterContext context,
dynamic config) =>
IconHelper.fromName(config.toString());
}
PLUGIN STUB
class IconPlugin extends IconPluginStub {
@override
Icon buildObject(
RouterContext context,
dynamic config) =>
IconHelper.fromName(config.toString());
}
PLUGIN STUB
voyager: ^1.1.3
IT’S ALL WIDGETS
IT’S ALL PATHS
DECOMPOSE WIDGETSHome
Hello World!
'/home':
widget: HomeWidget
title: Home
body: "Hello World"
icon: plus
target: "/details/plus"
DECOMPOSE
Home
Hello World!
'/home':
widget: HomeWidget
title: Home
body: "Hello World"
icon: plus
target: "/details/plus"
DECOMPOSE
Home
Hello World!
'/home':
widget: HomeWidget
title: Home
body: "Hello World"
fab: “/fab”
'/fab':
widget: FabWidget
icon: "plus"
target: “/details/plus"
voyager.codemagic.io/#/fab
'/fab':
widget: FabWidget
icon: "plus"
target: “/details/plus"
REMOTE CONFIG
'/fab':
widget: FabWidget
icon: "plus"
target: “/details/plus"
REMOTE CONFIG
'/fab':
widget: FabWidget
icon: "heart"
target: “/details/heart”
REMOTE CONFIG
'/fab':
widget: FabWidget
icon: "heart"
target: “/details/heart”
PATH OVERRIDE
router.clearCache();
router.registerPath( );
class RouterPath
NAVIGATE WITH ARGUMENT
Navigator
  .of(context)
  .pushNamed(pathDetail);
NAVIGATE WITH ARGUMENT
Navigator
  .of(context)
  .pushNamed(pathDetail,
arguments: icon);
VoyagerWidget(path: pathDetail);
OBJECT ARGUMENTS
VoyagerWidget(path: pathDetail,
argument: VoyagerArgument(icon));
OBJECT ARGUMENTS
EXTRACT ARGUMENT
final argument = Provider
.of<VoyagerArgument>(context);
final Icon icon = argument.value;
OBJECT MAPPING
class Shoe /object/Shoe
OBJECT MAPPING
'/object/Shoe':
type: shoe
widget: ShoeWidget
VoyagerWidget(path: pathShoe,
argument: VoyagerArgument( ));
OBJECT MAPPING
OBJECT MAPPING
'/object/Shoe':
type: shoe
widget: ShoeWidget
OBJECT MAPPING
‘/object/:class':
type: object
widget: %{class}Widget
LIST MAPPING
List<dynamic> ListView
ShoeWidget
BulbWidget
DuckWidget
LIST MAPPING
ListView.builder(
)
LIST MAPPING
ListView.builder(
itemCount: list.length,
)
LIST MAPPING
ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
final item = list[index];
return VoyagerWidget(
path: "/object/${item.runtimeType}",
argument: VoyagerArgument(item));
}
)
LIST MAPPING
ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
final item = list[index];
return VoyagerWidget(
path: "/object/${item.runtimeType}",
argument: VoyagerArgument(item));
}
)
LIST MAPPING
ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
final item = list[index];
return VoyagerWidget(
path: "/object/${item.runtimeType}",
argument: VoyagerArgument(item));
}
)
voyager_list: ^0.0.2
BLoC PLUGIN
Counter
Counter: 0 CounterBloc
increment
int
ThemeBloc
Counter
Counter: 1 CounterBloc
decrement
int
ThemeBloc
Counter
Counter: 0 CounterBloc
ThemeBloc
toggle
ThemeData
Counter
Counter: 0 CounterBloc
ThemeBloc
BLoC
'/counter':
widget: CounterPage
title: Counter
bloc:
- CounterBloc: 42
- ThemeBloc: dark
BLoC
BlocPluginBuilder()
.addBaseBloc<CounterBloc>((context, config, repo) =>
CounterBloc(config))
.addBaseBloc<ThemeBloc>((context, config, repo) =>
ThemeBloc(config))
.build()
BLoC
BlocPluginBuilder()
.addBaseBloc<CounterBloc>((context, config, repo) =>
CounterBloc(config))
.addBaseBloc<ThemeBloc>((context, config, repo) =>
ThemeBloc(config))
.build()
BLoC
BlocPluginBuilder()
.addBaseBloc<CounterBloc>((context, config, repo) =>
CounterBloc(config))
.addBaseBloc<ThemeBloc>((context, config, repo) =>
ThemeBloc(config))
.build()
BLoC
BlocPluginBuilder()
.addBaseBloc<CounterBloc>((context, config, repo) =>
CounterBloc(config))
.addBaseBloc<ThemeBloc>((context, config, repo) =>
ThemeBloc(config))
.build()
BLoC
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc(this._initialState) : super();
final int _initialState;
@override
int get initialState => _initalState;
/* TODO implement */
}
BLoC
final blocRepository = voyager.bloc;
BLoC
final blocRepository = voyager.bloc;
final bloc =
blocRepository.find<CounterBloc>();
BLoC
voyager_bloc: ^0.2.3
KEY TAKEAWAYS
SINGLE SOURCE OF TRUTH
READABILITY
SOURCE CODE
REQUIREMENTS
NAVIGATION, DEEP LINKS
DYNAMIC: A/B TESTS, REMOTE
CONFIG
THANK YOU
github.com/vishna/voyager
Łukasz Wiśniewski
@vishna
#voyager

More Related Content

What's hot

20130528 solution linux_frousseau_nopain_webdev
20130528 solution linux_frousseau_nopain_webdev20130528 solution linux_frousseau_nopain_webdev
20130528 solution linux_frousseau_nopain_webdevFrank Rousseau
 
How to migrate Cakephp 1.x to 2.x
How to migrate Cakephp 1.x to 2.xHow to migrate Cakephp 1.x to 2.x
How to migrate Cakephp 1.x to 2.xAndolasoft Inc
 
Twitter bootstrap
Twitter bootstrapTwitter bootstrap
Twitter bootstrapdennisdc
 
CodeStock :: Introduction To MacRuby and HotCocoa
CodeStock :: Introduction To MacRuby and HotCocoaCodeStock :: Introduction To MacRuby and HotCocoa
CodeStock :: Introduction To MacRuby and HotCocoaDoc Norton
 
Getting Started with DrupalGap
Getting Started with DrupalGapGetting Started with DrupalGap
Getting Started with DrupalGapAlex S
 
Using RequireJS with CakePHP
Using RequireJS with CakePHPUsing RequireJS with CakePHP
Using RequireJS with CakePHPStephen Young
 
Introduction to ZendX jQuery
Introduction to ZendX jQueryIntroduction to ZendX jQuery
Introduction to ZendX jQuerydennisdc
 
WordPress plugin #2
WordPress plugin #2WordPress plugin #2
WordPress plugin #2giwoolee
 
Dethroning Grunt: Simple and Effective Builds with gulp.js
Dethroning Grunt: Simple and Effective Builds with gulp.jsDethroning Grunt: Simple and Effective Builds with gulp.js
Dethroning Grunt: Simple and Effective Builds with gulp.jsJay Harris
 
SenchaCon 2016: Want to Use Ext JS Components with Angular 2? Here’s How to I...
SenchaCon 2016: Want to Use Ext JS Components with Angular 2? Here’s How to I...SenchaCon 2016: Want to Use Ext JS Components with Angular 2? Here’s How to I...
SenchaCon 2016: Want to Use Ext JS Components with Angular 2? Here’s How to I...Sencha
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overviewYehuda Katz
 

What's hot (16)

20130528 solution linux_frousseau_nopain_webdev
20130528 solution linux_frousseau_nopain_webdev20130528 solution linux_frousseau_nopain_webdev
20130528 solution linux_frousseau_nopain_webdev
 
How to migrate Cakephp 1.x to 2.x
How to migrate Cakephp 1.x to 2.xHow to migrate Cakephp 1.x to 2.x
How to migrate Cakephp 1.x to 2.x
 
Twitter bootstrap
Twitter bootstrapTwitter bootstrap
Twitter bootstrap
 
CodeStock :: Introduction To MacRuby and HotCocoa
CodeStock :: Introduction To MacRuby and HotCocoaCodeStock :: Introduction To MacRuby and HotCocoa
CodeStock :: Introduction To MacRuby and HotCocoa
 
Getting Started with DrupalGap
Getting Started with DrupalGapGetting Started with DrupalGap
Getting Started with DrupalGap
 
Using RequireJS with CakePHP
Using RequireJS with CakePHPUsing RequireJS with CakePHP
Using RequireJS with CakePHP
 
jQuery Mobile UI
jQuery Mobile UIjQuery Mobile UI
jQuery Mobile UI
 
实战Ecos
实战Ecos实战Ecos
实战Ecos
 
Introduction to ZendX jQuery
Introduction to ZendX jQueryIntroduction to ZendX jQuery
Introduction to ZendX jQuery
 
WordPress plugin #2
WordPress plugin #2WordPress plugin #2
WordPress plugin #2
 
Kickass
KickassKickass
Kickass
 
Dethroning Grunt: Simple and Effective Builds with gulp.js
Dethroning Grunt: Simple and Effective Builds with gulp.jsDethroning Grunt: Simple and Effective Builds with gulp.js
Dethroning Grunt: Simple and Effective Builds with gulp.js
 
SenchaCon 2016: Want to Use Ext JS Components with Angular 2? Here’s How to I...
SenchaCon 2016: Want to Use Ext JS Components with Angular 2? Here’s How to I...SenchaCon 2016: Want to Use Ext JS Components with Angular 2? Here’s How to I...
SenchaCon 2016: Want to Use Ext JS Components with Angular 2? Here’s How to I...
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overview
 
ApacheCon 2005
ApacheCon 2005ApacheCon 2005
ApacheCon 2005
 
@Ionic native/google-maps
@Ionic native/google-maps@Ionic native/google-maps
@Ionic native/google-maps
 

Similar to Voyager: The Widget Router

GDSC FCU 第3堂 Flutter
GDSC FCU 第3堂 FlutterGDSC FCU 第3堂 Flutter
GDSC FCU 第3堂 FlutterFCUGDSC
 
Adopting 3D Touch in your apps
Adopting 3D Touch in your appsAdopting 3D Touch in your apps
Adopting 3D Touch in your appsJuan C Catalan
 
Programming For Google Wave
Programming For Google WaveProgramming For Google Wave
Programming For Google WaveRodrigo Borges
 
Vue routing tutorial getting started with vue router
Vue routing tutorial getting started with vue routerVue routing tutorial getting started with vue router
Vue routing tutorial getting started with vue routerKaty Slemon
 
Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017Elyse Kolker Gordon
 
Cloud Endpoints _Polymer_ Material design by Martin Görner
Cloud Endpoints_Polymer_Material design by Martin GörnerCloud Endpoints_Polymer_Material design by Martin Görner
Cloud Endpoints _Polymer_ Material design by Martin GörnerEuropean Innovation Academy
 
jQuery Mobile and JavaScript
jQuery Mobile and JavaScriptjQuery Mobile and JavaScript
jQuery Mobile and JavaScriptGary Yeh
 
Quick Start to iOS Development
Quick Start to iOS DevelopmentQuick Start to iOS Development
Quick Start to iOS DevelopmentJussi Pohjolainen
 
Python Code Camp for Professionals 1/4
Python Code Camp for Professionals 1/4Python Code Camp for Professionals 1/4
Python Code Camp for Professionals 1/4DEVCON
 
React native introduction
React native introductionReact native introduction
React native introductionInnerFood
 
MOPCON 2014 - Best software architecture in app development
MOPCON 2014 - Best software architecture in app developmentMOPCON 2014 - Best software architecture in app development
MOPCON 2014 - Best software architecture in app developmentanistar sung
 
Cross Platform Mobile Development using Flutter by Wei Meng Lee at Mobile foc...
Cross Platform Mobile Development using Flutter by Wei Meng Lee at Mobile foc...Cross Platform Mobile Development using Flutter by Wei Meng Lee at Mobile foc...
Cross Platform Mobile Development using Flutter by Wei Meng Lee at Mobile foc...DevClub_lv
 
Building Reusable SwiftUI Components
Building Reusable SwiftUI ComponentsBuilding Reusable SwiftUI Components
Building Reusable SwiftUI ComponentsPeter Friese
 
22Flutter.pdf
22Flutter.pdf22Flutter.pdf
22Flutter.pdfdbaman
 

Similar to Voyager: The Widget Router (20)

GDSC FCU 第3堂 Flutter
GDSC FCU 第3堂 FlutterGDSC FCU 第3堂 Flutter
GDSC FCU 第3堂 Flutter
 
Adopting 3D Touch in your apps
Adopting 3D Touch in your appsAdopting 3D Touch in your apps
Adopting 3D Touch in your apps
 
Ios - Introduction to platform & SDK
Ios - Introduction to platform & SDKIos - Introduction to platform & SDK
Ios - Introduction to platform & SDK
 
Programming For Google Wave
Programming For Google WaveProgramming For Google Wave
Programming For Google Wave
 
Vue routing tutorial getting started with vue router
Vue routing tutorial getting started with vue routerVue routing tutorial getting started with vue router
Vue routing tutorial getting started with vue router
 
Hello Flutter
Hello FlutterHello Flutter
Hello Flutter
 
Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017
 
Cloud Endpoints _Polymer_ Material design by Martin Görner
Cloud Endpoints_Polymer_Material design by Martin GörnerCloud Endpoints_Polymer_Material design by Martin Görner
Cloud Endpoints _Polymer_ Material design by Martin Görner
 
jQuery Mobile and JavaScript
jQuery Mobile and JavaScriptjQuery Mobile and JavaScript
jQuery Mobile and JavaScript
 
pebble - Building apps on pebble
pebble - Building apps on pebblepebble - Building apps on pebble
pebble - Building apps on pebble
 
Play!ng with scala
Play!ng with scalaPlay!ng with scala
Play!ng with scala
 
Quick Start to iOS Development
Quick Start to iOS DevelopmentQuick Start to iOS Development
Quick Start to iOS Development
 
JavaScript on the Desktop
JavaScript on the DesktopJavaScript on the Desktop
JavaScript on the Desktop
 
Python Code Camp for Professionals 1/4
Python Code Camp for Professionals 1/4Python Code Camp for Professionals 1/4
Python Code Camp for Professionals 1/4
 
React native introduction
React native introductionReact native introduction
React native introduction
 
Prototype UI
Prototype UIPrototype UI
Prototype UI
 
MOPCON 2014 - Best software architecture in app development
MOPCON 2014 - Best software architecture in app developmentMOPCON 2014 - Best software architecture in app development
MOPCON 2014 - Best software architecture in app development
 
Cross Platform Mobile Development using Flutter by Wei Meng Lee at Mobile foc...
Cross Platform Mobile Development using Flutter by Wei Meng Lee at Mobile foc...Cross Platform Mobile Development using Flutter by Wei Meng Lee at Mobile foc...
Cross Platform Mobile Development using Flutter by Wei Meng Lee at Mobile foc...
 
Building Reusable SwiftUI Components
Building Reusable SwiftUI ComponentsBuilding Reusable SwiftUI Components
Building Reusable SwiftUI Components
 
22Flutter.pdf
22Flutter.pdf22Flutter.pdf
22Flutter.pdf
 

Recently uploaded

Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideChristina Lin
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionSolGuruz
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfkalichargn70th171
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataBradBedford3
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number SystemsJheuzeDellosa
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...MyIntelliSource, Inc.
 
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️anilsa9823
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...OnePlan Solutions
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto González Trastoy
 
Active Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfActive Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfCionsystems
 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsAndolasoft Inc
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)OPEN KNOWLEDGE GmbH
 
why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfjoe51371421
 
Project Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationProject Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationkaushalgiri8080
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...stazi3110
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
 
Test Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and BackendTest Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and BackendArshad QA
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxComplianceQuest1
 

Recently uploaded (20)

Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number Systems
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
 
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
Active Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfActive Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdf
 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.js
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)
 
why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdf
 
Project Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationProject Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanation
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
Exploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the ProcessExploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the Process
 
Test Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and BackendTest Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and Backend
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 

Voyager: The Widget Router