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
Voyager: The Widget Router
'/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

PPTX
Angular + Components
PDF
Mobile Application Development with JUCE and Native API’s
PDF
WebcampZG - Rails 4
PPTX
JS Fest 2018. Илья Иванов. Введение в React-Native
PDF
Replacing ActiveRecord callbacks with Pub/Sub
PDF
Javascript Application Architecture with Backbone.JS
PDF
Rails 3: Dashing to the Finish
PDF
20130528 solution linux_frousseau_nopain_webdev
Angular + Components
Mobile Application Development with JUCE and Native API’s
WebcampZG - Rails 4
JS Fest 2018. Илья Иванов. Введение в React-Native
Replacing ActiveRecord callbacks with Pub/Sub
Javascript Application Architecture with Backbone.JS
Rails 3: Dashing to the Finish
20130528 solution linux_frousseau_nopain_webdev

What's hot (15)

DOC
How to migrate Cakephp 1.x to 2.x
PPTX
Twitter bootstrap
KEY
CodeStock :: Introduction To MacRuby and HotCocoa
PDF
Getting Started with DrupalGap
PDF
Using RequireJS with CakePHP
PPTX
jQuery Mobile UI
PDF
实战Ecos
PPT
Introduction to ZendX jQuery
PPTX
WordPress plugin #2
KEY
PDF
Dethroning Grunt: Simple and Effective Builds with gulp.js
PPTX
SenchaCon 2016: Want to Use Ext JS Components with Angular 2? Here’s How to I...
PDF
Rails 3 overview
PDF
ApacheCon 2005
PDF
@Ionic native/google-maps
How to migrate Cakephp 1.x to 2.x
Twitter bootstrap
CodeStock :: Introduction To MacRuby and HotCocoa
Getting Started with DrupalGap
Using RequireJS with CakePHP
jQuery Mobile UI
实战Ecos
Introduction to ZendX jQuery
WordPress plugin #2
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...
Rails 3 overview
ApacheCon 2005
@Ionic native/google-maps
Ad

Similar to Voyager: The Widget Router (9)

PDF
Flutter workshop @ bang saen 2020
PDF
Introduction to flutter
PDF
22Flutter.pdf
PPTX
All a flutter about Flutter.io
PDF
Cross Platform Mobile Development using Flutter by Wei Meng Lee at Mobile foc...
PPTX
Lecture 5 _ Building Layouts (1).pptx
PDF
Pune Flutter Presents - Flutter 101
PPTX
Google I/O 2018 Extended, Baghdad - Flutter
PDF
Flutter beyond hello world
Flutter workshop @ bang saen 2020
Introduction to flutter
22Flutter.pdf
All a flutter about Flutter.io
Cross Platform Mobile Development using Flutter by Wei Meng Lee at Mobile foc...
Lecture 5 _ Building Layouts (1).pptx
Pune Flutter Presents - Flutter 101
Google I/O 2018 Extended, Baghdad - Flutter
Flutter beyond hello world
Ad

Recently uploaded (20)

PPTX
"Secure File Sharing Solutions on AWS".pptx
PPTX
Introduction to Windows Operating System
PPTX
CNN LeNet5 Architecture: Neural Networks
PDF
Salesforce Agentforce AI Implementation.pdf
PDF
Introduction to Ragic - #1 No Code Tool For Digitalizing Your Business Proces...
PPTX
Weekly report ppt - harsh dattuprasad patel.pptx
PDF
BoxLang Dynamic AWS Lambda - Japan Edition
PPTX
Matchmaking for JVMs: How to Pick the Perfect GC Partner
PPTX
Cybersecurity-and-Fraud-Protecting-Your-Digital-Life.pptx
PPTX
Airline CRS | Airline CRS Systems | CRS System
DOCX
How to Use SharePoint as an ISO-Compliant Document Management System
PDF
How AI/LLM recommend to you ? GDG meetup 16 Aug by Fariman Guliev
PDF
Multiverse AI Review 2025: Access All TOP AI Model-Versions!
PDF
novaPDF Pro 11.9.482 Crack + License Key [Latest 2025]
PDF
Visual explanation of Dijkstra's Algorithm using Python
PDF
Autodesk AutoCAD Crack Free Download 2025
PDF
AI Guide for Business Growth - Arna Softech
PDF
DuckDuckGo Private Browser Premium APK for Android Crack Latest 2025
PPTX
Tech Workshop Escape Room Tech Workshop
PDF
AI-Powered Threat Modeling: The Future of Cybersecurity by Arun Kumar Elengov...
"Secure File Sharing Solutions on AWS".pptx
Introduction to Windows Operating System
CNN LeNet5 Architecture: Neural Networks
Salesforce Agentforce AI Implementation.pdf
Introduction to Ragic - #1 No Code Tool For Digitalizing Your Business Proces...
Weekly report ppt - harsh dattuprasad patel.pptx
BoxLang Dynamic AWS Lambda - Japan Edition
Matchmaking for JVMs: How to Pick the Perfect GC Partner
Cybersecurity-and-Fraud-Protecting-Your-Digital-Life.pptx
Airline CRS | Airline CRS Systems | CRS System
How to Use SharePoint as an ISO-Compliant Document Management System
How AI/LLM recommend to you ? GDG meetup 16 Aug by Fariman Guliev
Multiverse AI Review 2025: Access All TOP AI Model-Versions!
novaPDF Pro 11.9.482 Crack + License Key [Latest 2025]
Visual explanation of Dijkstra's Algorithm using Python
Autodesk AutoCAD Crack Free Download 2025
AI Guide for Business Growth - Arna Softech
DuckDuckGo Private Browser Premium APK for Android Crack Latest 2025
Tech Workshop Escape Room Tech Workshop
AI-Powered Threat Modeling: The Future of Cybersecurity by Arun Kumar Elengov...

Voyager: The Widget Router