## Quick Start In this tutorial you'll create a simple Flutter app that supports rich text editing with Zefyr. What you'll learn: * How to create a new screen for the editor * Basic widget layout required by Zefyr * How to load and save documents using JSON serialization ### 01. Create a new Flutter project If you haven't installed Flutter yet then [install it first](https://flutter.dev/docs/get-started/install). Create a new project using Terminal and `flutter create` command: ```shell $ flutter create myapp $ cd myapp ``` For more methods of creating a project see [official documentation](https://flutter.dev/docs/get-started/test-drive). ### 02. Add Zefyr to your new project Add `zefyr` package as a dependency to `pubspec.yaml` of your new project: ```yaml dependencies: zefyr: [latest_version] ``` And run `flutter packages get`. This installs [zefyr](https://pub.dev/packages/zefyr) and all required dependencies, including [notus](https://pub.dev/packages/notus) package which implements Zefyr's document model. > Notus package is platform-agnostic and can be used outside of Flutter apps, > that is, on the web or server-side. ### 03. Create editor page We start by creating a `StatefulWidget` that will be responsible for handling all the state and interactions with Zefyr. In this example we'll assume that there is dedicated editor page in our app. Create a new file `lib/src/editor_page.dart` and type in (or paste) the following: ```dart import 'package:flutter/material.dart'; import 'package:quill_delta/quill_delta.dart'; import 'package:zefyr/zefyr.dart'; class EditorPage extends StatefulWidget { @override EditorPageState createState() => EditorPageState(); } class EditorPageState extends State { /// Allows to control the editor and the document. ZefyrController _controller; /// Zefyr editor like any other input field requires a focus node. FocusNode _focusNode; @override void initState() { super.initState(); // Here we must load the document and pass it to Zefyr controller. final document = _loadDocument(); _controller = new ZefyrController(document); _focusNode = new FocusNode(); } @override Widget build(BuildContext context) { // Note that the editor requires special `ZefyrScaffold` widget to be // one of its parents. return Scaffold( appBar: AppBar(title: Text("Editor page")), body: ZefyrScaffold( child: ZefyrEditor( padding: EdgeInsets.all(16), controller: _controller, focusNode: _focusNode, ), ), ); } /// Loads the document to be edited in Zefyr. NotusDocument _loadDocument() { // For simplicity we hardcode a simple document with one line of text // saying "Zefyr Quick Start". // (Note that delta must always end with newline.) final Delta delta = Delta()..insert("Zefyr Quick Start\n"); return NotusDocument.fromDelta(delta); } } ``` Above example widget creates a page with an `AppBar` and Zefyr editor in its body. We also initialize our editor with a simple one-line document. Now we need to wire it up with our app. Open `lib/main.dart` and replace autogenerated contents with this: ```dart import 'package:flutter/material.dart'; import 'src/editor_page.dart'; void main() { runApp(QuickStartApp()); } class QuickStartApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Quick Start', home: HomePage(), routes: { "/editor": (context) => EditorPage(), }, ); } } class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { final navigator = Navigator.of(context); return Scaffold( appBar: AppBar(title: Text("Quick Start")), body: Center( child: FlatButton( child: Text("Open editor"), onPressed: () => navigator.pushNamed("/editor"), ), ), ); } } ``` Here is how it might look when we run the app and navigate to editor page: At this point we can already edit the document and apply styles, however if we navigate back from this page our changes will be lost. Let's fix this and add a button which saves the document to the device's file system. First we need a function to save the document. Update `lib/src/editor_page.dart` as follows: ```dart // change: add these two lines to imports section at the top of the file import 'dart:convert'; // access to jsonEncode() import 'dart:io'; // access to File and Directory classes class EditorPageState extends State { // change: add after _loadDocument() void _saveDocument(BuildContext context) { // Notus documents can be easily serialized to JSON by passing to // `jsonEncode` directly final contents = jsonEncode(_controller.document); // For this example we save our document to a temporary file. final file = File(Directory.systemTemp.path + "/quick_start.json"); // And show a snack bar on success. file.writeAsString(contents).then((_) { Scaffold.of(context).showSnackBar(SnackBar(content: Text("Saved."))); }); } } ``` This function converts our document using `jsonEncode()` function and writes the result to a file `quick_start.json` in the system's temporary directory. Note that `File.writeAsString` is an asynchronous method and returns Dart's `Future`. This is why we register a completion callback with a call to `Future.then`. One more important bit here is that we pass `BuildContext` argument to `_saveDocument`. This is required to get access to our page's `Scaffold` state, so that we can show a `SnackBar`. Now we just need to add a button to the AppBar, so we need to modify `build` method as follows: ```dart class EditorPageState extends State { // change: replace build() method with following @override Widget build(BuildContext context) { // Note that the editor requires special `ZefyrScaffold` widget to be // present somewhere up the widget tree. return Scaffold( appBar: AppBar( title: Text("Editor page"), // <<< begin change actions: [ Builder( builder: (context) => IconButton( icon: Icon(Icons.save), onPressed: () => _saveDocument(context), ), ) ], // end change >>> ), body: ZefyrScaffold( child: ZefyrEditor( padding: EdgeInsets.all(16), controller: _controller, focusNode: _focusNode, ), ), ); } } ``` We have to use `Builder` here for our icon button because we need access to build context which has access to `Scaffold` widget's state. Now we can reload our app, hit "Save" button and see the snack bar. Since we now have this document saved to a file, let's update our `_loadDocument` method to load saved file if it exists.