Using Fauna as the Data API of a Flutter App
Tags: fauna, flutter, mobile, serverless
Published: 2021-12-07 18:52:05

---
title: Using Fauna as the Data API of a Flutter App
published: true
description: A brief tutorial on how to use Fauna's serverless database as the Data API to a Flutter App.
tags: fauna, flutter, mobile, serverless
//cover_image: https://direct_url_to_image.jpg
---

Unless you are building an offline app or consuming someone else’s API, when developing a mobile app, most times you will have the burden of setting up a database and adding an API layer so you can consume that data.

Of all the options out there, [Fauna](http://fauna.com/?utm_source=DevTo&utm_medium=referral&utm_campaign=WritewithFauna_FaunaFlutter_JSinkala) is among the few that I have personally found myself working with the most [due to various advantages it holds over others](https://dev.to/xinnks/bottlenecks-of-firebase-vs-fauna-51d2). It gives me the playground to experiment with things without having to worry much about breaking my app or the bank. I have loved seeing it grow since I first started using it.

In the mobile app environment, especially hybrid app development I have never looked back since I came across Flutter; it has been my go to toolkit when developing for fun or work. One of Flutter’s major advantages is its hot reload, which helps speed up the development process. As a developer, I appreciate that with it, I can see changes to code almost immediately without having to restart the app.

I recently worked on a mobile app with these two technologies and given that project’s complexity and how this stack simplified the work on the development side I was pleased with the results.

In this article, we are going to see how these two technologies simply work together on the making of a small data listing mobile app.



## The App we are building - ‘My Movies’

To see the Flutter-Fauna stack in practice we are going to create ‘My Movies’. A simple movie information mobile app made with Flutter and Fauna as its data API.

The ‘My Movies’ app has three screens. A home screen which will be listing all of the movies on our database, a movie details screen which will be showing detailed movie information and a search page where users can search movies by title.



## Preparing the database

If you do not have a Fauna account, head over to [Fauna](https://dashboard.fauna.com/accounts/register?utm_source=DevTo&utm_medium=referral&utm_campaign=WritewithFauna_FaunaFlutter_JSinkala)’s registration page and create one.

Create a new database and name it **my_movies,** and inside, add a **movies** collection in which we’ll be storing the movies data .Afterwards, create a new index and name it **all_movies.**

```js
CreateIndex({
 name: "all_movies",
 unique: false,
 serialized: true,
 source: Collection(""movies"),
 values: [
  {
   field: ["data", "title"]
  },
  {
   field: ["data", "runtime"]
  },
  {
   field: ["data", "plot"]
  },
  {
   field: ["data", "genre"]
  },
  {
   field: ["data", "poster"]
  },
  {
   field: ["ref", "id"]
  }
 ]
})
```



Next, create a new Admin key by clicking on *Security* in the left sidebar, followed by clicking the *New key* button. Copy the key’s secret displayed on the next page as it is never displayed again, secure it safely.

![Creating a new security key in Fauna](https://lh4.googleusercontent.com/QyyCJV_53idGr3Wv3gHBfZY_I1WWKnwiAcU8cZnqA0Dazxb6W8fvpgZ0K9dmgxuBJInHc0oVhXn8tr50KFAIH1WJSjoE-1_roOW4IRsP3XysLJhlaH8trLVDHatqRL0IpLdCRHgY)

Run the Fauna FQL (Fauna Query Language) script found on this [gist](https://gist.github.com/xinnks/333590c9d379ee716eb7381581eff0f6) on the dashboard shell or locally if you have installed [Fauna-shell](https://github.com/fauna/fauna-shell) to populate the **movies** collection with some initial data.

![Accessing Fauna's online shell](https://lh4.googleusercontent.com/PGcYAFatiHwr3XW1WJVfSYQOyG4u5bIvALD_XB1INcrECWw8oIYagI7u976K4VSvo4idHV_2Q2gLPlugWgx7PaXH3wqlJFThJl_eF_NLSlOmenQzx-mWFACBgDEoWUSjHCJZ1PN0)



## Setting up the Flutter project

Start by creating a new flutter project.

```she
flutter create my_movies_app
```

We are going to use an unofficial Fauna package for Flutter [flutterdb_http](https://pub.dev/packages/faunadb_http) to make FQL queries against our database.

Add the package to the dependencies:

```shell
flutter pub add faunadb_http
```

We’ll use Bloc to separate our business logic from the presentational layer, hence proceed to installing the following packages:

- [flutter_bloc](https://pub.dev/packages/flutter_bloc)

```she
flutter pub add flutter_bloc
```

- [bloc](https://pub.dev/packages/bloc) 

```shell
flutter pub add bloc
```

- [equatable](https://pub.dev/packages/equatable)

```she
flutter pub add equatable
```



Let’s proceed with setting up our Movie data model:

```dart
/// Movie model

class Movie {
  final String title;
  final double runtime;
  final String plot;
  final String genre;
  final String poster;
  final String refId;
  final double rating;

  Movie(this.title,
      {required this.runtime,
      required this.plot,
      required this.genre,
      required this.poster,
      required this.refId,
      this.rating = 0.0});

  Map<String, Object> toJson() {
    return {
      "title": title,
      "runtime": runtime,
      "plot": plot,
      "genre": genre,
      "poster": poster,
      "refId": refId,
      "rating": rating
    };
  }

  factory Movie.fromJson(Map<String, dynamic> json) => Movie(json["title"],
      runtime: double.parse(json["runtime"].toString()),
      plot: json["plot"],
      genre: json["genre"],
      poster: json["poster"] ??
          'default_movie_poster_url',
      refId: json['ref_id']);
}
```




### The App’s Events and States

Next we’ll create the **MoviesEvent** class which will work with the bloc layer to define our app’s expected events.
These are the events that we expect to be performed in our app.

```dart
/// blocs/movies/event.dart

abstract class MoviesEvent extends Equatable {
  const MoviesEvent();
  @override
  List<Object> get props => [];
}

class HomeScreenInitial extends MoviesEvent {}

class FetchMovies extends MoviesEvent {}

class SearchScreenInitial extends MoviesEvent {}

class SubmitSearch extends MoviesEvent {
  final String searchQuery;
  const SubmitSearch(this.searchQuery);
  @override
  List<Object> get props => [searchQuery];
  @override
  String toString() => 'MoviesLoad { searchQuery: $searchQuery }';
}
```

Let’s also set up the bloc’s state for all the states we expect the app to have resulting from the above events and data transactions with the data repositories.

```shell
/// blocs/movies/state.dart

class MoviesState extends Equatable {
  const MoviesState();
  @override
  List<Object> get props => [];
}

class MoviesStateInitial extends MoviesState {}

class LoadingState extends MoviesState {}

class MoviesFetched extends MoviesState {
  final List<Movie> movies;
  const MoviesFetched([this.movies = const []]);
  @override
  List<Object> get props => [movies];
  @override
  String toString() => "MoviesLoadSuccess {Movies: $movies.toString()}";
}


class MoviesFetchingFailure extends MoviesState {
  final String errorMessage;
  const MoviesFetchingFailure({this.errorMessage = "Experienced some error"});
  @override
  List<Object> get props => [errorMessage];
  @override
  String toString() => 'MoviesFetchingFailure { Movie: $Movie.toString() }';
}

class SearchScreenInitialState extends MoviesState {
  final String message;
  const SearchScreenInitialState(
      {this.message = "Type above to search for movies"});
  @override
  List<Object> get props => [message];
  @override
  String toString() => 'SearchScreenInitialState { message: $message }';
}

class SearchFailure extends MoviesState {
  final String errorMessage;
  const SearchFailure({this.errorMessage = "Experienced some error"});
  @override
  List<Object> get props => [errorMessage];
  @override
  String toString() => 'MoviesFetchingFailure { Movie: $Movie.toString() }';
}

class SearchResultsFetched extends MoviesState {
  final List<Movie> movies;
  const SearchResultsFetched([this.movies = const []]);
  @override
  List<Object> get props => [movies];
  @override
  String toString() => "SearchResultsFetched {Movies: $movies.toString()}";
}
```

Lets create a router class that’s going to help us navigate around our app.

```shell
/// app_router.dart

class AppRouter {
  Route onGenerateRoute(RouteSettings settings) {
    switch (settings.name) {
      case "movie-details":
        return MaterialPageRoute(
            builder: (_) =>
                MovieDetailsScreen(movie: settings.arguments as Movie));
      case "search":
        return MaterialPageRoute(builder: (_) => const SearchScreen());
      default:
        return MaterialPageRoute(builder: (_) => const HomeScreen());
    }
  }
}
```



Create an intermediary **AppState** widget that will be the root node providing the **MoviesBloc** to the rest of the widget tree and disposing it when not needed.

```dart
/// app_state.dart

class AppState extends StatefulWidget {
  const AppState({Key? key}) : super(key: key);
  @override
  _AppState createState() => _AppState();
}

class _AppState extends State<AppState> {
  final _appRouter = AppRouter();
  late final MoviesBloc _moviesBloc;
  _AppState()
      : _moviesBloc = MoviesBloc(moviesRepository: MovieDataRepository()),
        super();

  @override
  Widget build(BuildContext context) {
    return BlocProvider.value(
      value: _moviesBloc,
      child: MaterialApp(
        onGenerateRoute: _appRouter.onGenerateRoute,
        debugShowCheckedModeBanner: false,
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    _moviesBloc.close();
  }
}
```



Set **AppState** as the child widget of our root widget **MyApp**.

```dart
/// main.dart

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const AppState();
  }
}
```




### Setting up the data layer

To consume data for our app in a clean and functional manner we’ll set up a data layer that involves a movies *provider* which will be fetching raw data for us from Fauna and a movies *repository* which will be transacting data between the provider and bloc. From the bloc, data can be presented to our pages through states.

Start off by configuring the Fauna client that will enable us to send FQL queries to Fauna, place this in it’s own class to prevent code repetition.

```dart
/// faunadb_service.dart


class FaunaDBInstance {
  late final FaunaConfig config;
  late final FaunaClient client;

  FaunaDBInstance(String secret) {
    config = FaunaConfig.build(
      secret: secret,
    );
    client = FaunaClient(config);
  }

  FaunaClient faunaDBClient() {
    return client;
  }

  faunaClose() {
    client.close();
  }
}
```

We follow this up by setting up our data provider **FaunaDBMoviesProvider**.

```dart
/// providers/faunadb_movies_provider.dart

class FaunaDBMoviesProvider {
  late FaunaDBInstance _fauna;
  late FaunaClient _client;

  FaunaDBMoviesProvider() {
    _fauna = FaunaDBInstance('SECRET_KEY');
    _client = _fauna.faunaDBClient();
  }

  Future<dynamic> movies() async {
    final requestQuery = Map_(
        Paginate(Match(Index("all_movies"))),
        Lambda(
            "movieDoc",
            Let(
                {'movie': Var('movieDoc')},
                Obj({
                  'title': Select(0, Var('movie')),
                  'runtime': Select(1, Var('movie')),
                  'plot': Select(2, Var('movie')),
                  'genre': Select(3, Var('movie')),
                  'poster': Select(4, Var('movie')),
                  'ref_id': Select(5, Var('movie')),
                }))));
    try {
      FaunaResponse? response = await _client.query(requestQuery);
      return response;
    } catch (e) {
      rethrow;
    }
  }

  Future<dynamic> searchForMovies(String searchQuery) async {
    final requestQuery = Map_(
        Paginate(Match(Index("all_movies"))),
        Lambda(
            "movieDoc",
            Let(
                {'movie': Var('movieDoc')},
                Obj({
                  'title': Select(0, Var('movie')),
                  'runtime': Select(1, Var('movie')),
                  'plot': Select(2, Var('movie')),
                  'genre': Select(3, Var('movie')),
                  'poster': Select(4, Var('movie')),
                  'ref_id': Select(5, Var('movie')),
                }))));
    try {
      FaunaResponse? response = await _client.query(requestQuery);
      return response;
    } catch (e) {
      rethrow;
    }
  }
}
```



Choose any amongst the many possible ways to secure your **secret_key**, a topic I won’t be getting into here. You should never hardcode it inside your code.

Next we’ll set up a repository **MovieDataRepository** that will be in charge of supplying data to the **MoviesBloc**.

With this setup we get the advantage of having multiple providers supplying data to a single repository without having to change much of the App’s core codebase.

```dart
/// repositories/faunadb_movies_repository.dart

class MovieDataRepository {
  final FaunaDBMoviesProvider _faunaDBMoviesProvider;

  MovieDataRepository()
      : _faunaDBMoviesProvider = FaunaDBMoviesProvider(),
        super();

  Future<dynamic> movies() async {
    FaunaResponse response = await _faunaDBMoviesProvider.movies();
    if (response.hasErrors) {
      return response.errors.toString();
    } else {
      if (response.asMap()['resource']['data'].length > 0) {
        return List<Movie>.from(
            response.asMap()['resource']['data'].map((item) {
          return Movie.fromJson(item);
        }));
      }
      return [];
    }
  }

  Future<dynamic> searchForMovies(String searchQuery) async {
    FaunaResponse response =
        await _faunaDBMoviesProvider.searchForMovies(searchQuery);
    if (response.hasErrors) {
      return response.errors.toString();
    } else {
      if (response.asMap()['resource']['data'].length > 0) {
        List<Movie> searchresults = [];
        List<Movie> allMovies =
            List<Movie>.from(response.asMap()['resource']['data'].map((item) {
          return Movie.fromJson(item);
        }));
        for (Movie movie in allMovies) {
          if (movie.title.toLowerCase().contains(searchQuery.toLowerCase())) {
            searchresults.add(movie);
          }
        }
        return searchresults;
      }
      return [];
    }
  }
}
```



Inside the **MovieDataRepository** class we see the *faunadb_http* package at work helping us make native FQL queries just as we would have on any other setting involving Fauna data and FQL queries.

For any function that conflicts with Dart’s, just add an underscore as the function’s suffix. E.g we’d have Map_() instead of Map() so as not to conflict the two.

Finally we set up our **MoviesBloc** which up to this point has been referenced countlessly.

```dart
/// bloc/movies/bloc.dart

class MoviesBloc extends Bloc<MoviesEvent, MoviesState> {
  final MovieDataRepository moviesRepository;
  MoviesBloc({required this.moviesRepository}) : super(MoviesStateInitial());

  @override
  Stream<MoviesState> mapEventToState(MoviesEvent event) async* {
    if (event is MoviesScreenInitial) {
      yield* _mapMoviesStateInitialToState(event);
    }
    if (event is FetchMovies) {
      yield* _mapFetchMoviesToState();
    }
    if (event is SearchScreenInitial) {
      yield const SearchScreenInitialState();
    }
    if (event is SubmitSearch) {
      yield LoadingState();
      yield* _mapSubmitSearchToState(event);
    }
  }

  Stream<MoviesState> _mapMoviesStateInitialToState(
      MoviesScreenInitial event) async* {
    yield LoadingState();
    var movies = await moviesRepository.movies();
    if (movies is! List<Movie>) {
      yield const MoviesFetchingFailure(errorMessage: "Some Error!");
    }
    if (movies is List<Movie>) {
      yield MoviesFetched(movies);
    }
  }

  Stream<MoviesState> _mapFetchMoviesToState() async* {
    var movies = await moviesRepository.movies();
    if (movies is! List<Movie>) {
      yield MoviesFetchingFailure(errorMessage: movies.toString());
    }
    if (movies is List<Movie>) {
      yield MoviesFetched(movies);
    }
  }

  Stream<MoviesState> _mapSubmitSearchToState(SubmitSearch event) async* {
    var movies = await moviesRepository.searchForMovies(event.searchQuery);
    if (movies is! List<Movie>) {
      yield MoviesFetchingFailure(errorMessage: movies.toString());
    }
    if (movies is List<Movie>) {
      yield SearchResultsFetched(movies);
    }
  }
}
```



All that is left is to add our presentational layer to render all the above configuration useful.



### The Presentational Layer

On the **HomeScreen** we use a **BlocConsumer** that gives us the ability to listen to states and react programmatically to them on top of the builder that enables us to rebuild our widgets.

```dart
/// screens/home_screen.dart

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);
  @override
  _HomeScreen createState() => _HomeScreen();
}

class _HomeScreen extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    final _moviesBloc = BlocProvider.of<MoviesBloc>(context);
    _moviesBloc.add(MoviesScreenInitial());

    return BlocConsumer<MoviesBloc, MoviesState>(
        listener: (context, state) {},
        builder: (context, state) {
          Widget widgetToDisplay = const SizedBox();
          if (state is LoadingState) {
            widgetToDisplay = Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: const [
                Expanded(
                  child: Center(
                    child: CircularProgressIndicator(),
                  ),
                )
              ],
            );
          }
          if (state is MoviesFetchingFailure) {
            widgetToDisplay = Column(
              children: [
                Expanded(
                  child: Center(
                    child: Text(state.errorMessage),
                  ),
                )
              ],
            );
          }
          if (state is MoviesFetched) {
            widgetToDisplay = state.movies.isNotEmpty
                ? ListView(
                    children: List<Widget>.from(
                        state.movies.map((movie) => MovieListItem(movie))))
                : Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: const [
                      Expanded(
                        child: Center(
                          child: Text("No Movies in here!"),
                        ),
                      )
                    ],
                  );
          }
          return Scaffold(
            appBar: AppBar(
              title: const Text("My Movies"),
              elevation: .5,
              actions: [
                IconButton(
                    onPressed: () {
                      Navigator.pushNamed(context, "search");
                    },
                    icon: const Icon(
                      Icons.search,
                      color: Colors.white,
                    ))
              ],
            ),
            body: SafeArea(
              child: widgetToDisplay,
            ),
          );
        });
  }
}
```

We discern the states we receive from **MoviesBloc** and convey the received states to the screen.

We do the same for the **SearchScreen** along with adding a form field which the searches will be typed into.

```dart
/// screens/search_screen.dart

class SearchScreen extends StatefulWidget {
  const SearchScreen({Key? key}) : super(key: key);
  @override
  _SearchScreenState createState() => _SearchScreenState();
}

class _SearchScreenState extends State<SearchScreen> {
  @override
  Widget build(BuildContext searchScreenContext) {
    final _moviesBloc = BlocProvider.of<MoviesBloc>(searchScreenContext);
    _moviesBloc.add(SearchScreenInitial());
    TextEditingController searchQuery = TextEditingController();
    void _onSubmission(String query) {
      _moviesBloc.add(SubmitSearch(query));
    }

    return BlocConsumer<MoviesBloc, MoviesState>(
        listener: (context, state) {},
        builder: (context, state) {
          Widget widgetToDisplay = const Center();
          if (state is SearchScreenInitialState) {
            widgetToDisplay = Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Expanded(
                  child: Center(
                    child: Text(state.message),
                  ),
                )
              ],
            );
          }
          if (state is LoadingState) {
            widgetToDisplay = Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: const [
                Expanded(
                  child: Center(
                    child: CircularProgressIndicator(),
                  ),
                )
              ],
            );
          }
          if (state is SearchResultsFetched) {
            widgetToDisplay = Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: const [
                Expanded(
                  child: Center(
                    child: CircularProgressIndicator(),
                  ),
                )
              ],
            );
          }
          if (state is MoviesFetchingFailure) {
            widgetToDisplay = Column(
              children: [
                Expanded(
                  child: Center(
                    child: Text(state.errorMessage),
                  ),
                )
              ],
            );
          }
          if (state is SearchResultsFetched) {
            widgetToDisplay = state.movies.isNotEmpty
                ? ListView(
                    children: List<Widget>.from(
                        state.movies.map((movie) => MovieListItem(movie))))
                : Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: const [
                      Expanded(
                        child: Center(
                          child: Text("No Movies in here with that title!"),
                        ),
                      )
                    ],
                  );
          }

          return Scaffold(
            appBar: AppBar(
              leading: BackButton(
                onPressed: () => Navigator.of(context)
                    .pushNamedAndRemoveUntil("home", (route) => false),
              ),
              title: TextFormField(
                controller: searchQuery,
                onFieldSubmitted: _onSubmission,
                cursorColor: Colors.white,
                decoration: const InputDecoration(
                    hintText: "Search",
                    hintStyle: TextStyle(
                      color: Colors.white,
                    )),
                style: const TextStyle(
                  color: Colors.white,
                ),
              ),
            ),
            body: SafeArea(
              child: widgetToDisplay,
            ),
          );
        });
  }
}
```



For the movie details screen just as seen on the **AppRoute,** we are passing **[RouteSettings](https://api.flutter.dev/flutter/widgets/RouteSettings-class.html)** to it with the movie data as the arguments parameter.

```dart
/// app_router.dart
...
case "movie-details":
        return MaterialPageRoute(
            builder: (_) =>
                MovieDetailsScreen(movie: settings.arguments as Movie));
...
```



Let’s create the **MovieListItem** widget used to plot a movie item on the movie lists.

```dart
/// components/movie_list_item.dart

class MovieListItem extends StatefulWidget {
  const MovieListItem(this.movie, {Key? key}) : super(key: key);
  final Movie movie;
  @override
  _MovieListItemState createState() => _MovieListItemState();
}

class _MovieListItemState extends State<MovieListItem> {
  @override
  Widget build(BuildContext context) {
    return TextButton(
        onPressed: () => Navigator.pushNamed(context, "movie-details",
            arguments: widget.movie),
        child: ListTile(
          leading: SizedBox(
            width: 40,
            child: ClipRRect(
              borderRadius: BorderRadius.circular(10.0),
              child: Image.network(
                      widget.movie.poster,
                      fit: BoxFit.cover,
                    ),
            ),
          ),
          title: Text(widget.movie.title),
          subtitle: Text(widget.movie.genre),
        ));
  }
}
```



And lastly we have our **MovieDetailsScreen** which will be presenting the movie data passed from the above component.

```dart
/// screens/movie_details_screen.dart

class MovieDetailsScreen extends StatefulWidget {
  const MovieDetailsScreen({Key? key, required this.movie}) : super(key: key);
  final Movie movie;

  @override
  _MovieDetailsScreenState createState() => _MovieDetailsScreenState();
}

class _MovieDetailsScreenState extends State<MovieDetailsScreen> {

  @override
  Widget build(BuildContext context) {

    Size deviceConstraints = MediaQuery.of(context).size;
    double topContainerHeight =
        deviceConstraints.height > 600.0 ? deviceConstraints.height / 2 : 300.0;
    double coverWidth = deviceConstraints.width * (2 / 3) - 45;
    double detailsWidth = deviceConstraints.width * (1 / 3) - 45;

    return Scaffold(
        appBar: AppBar(
          backgroundColor: Colors.white,
          elevation: .5,
          leading: const BackButton(color: Colors.black),
        ),
        body: SafeArea(
            child: ListView(
          padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20),
          children: [
            Container(
                height: topContainerHeight,
                decoration: BoxDecoration(
                    color: Colors.yellow[50],
                    borderRadius: const BorderRadius.all(Radius.circular(30))),
                child: Row(
                  children: [
                    Container(
                      padding: const EdgeInsets.only(right: 10),
                      child: Column(children: [
                        SizedBox(
                          width: coverWidth,
                          child: ClipRRect(
                            clipBehavior: Clip.hardEdge,
                            borderRadius:
                                const BorderRadius.all(Radius.circular(30)),
                            child: Image.network(
                              widget.movie.poster,
                              fit: BoxFit.cover,
                              height: topContainerHeight,
                            ),
                          ),
                        )
                      ]),
                    ),
                    Container(
                      padding: const EdgeInsets.only(left: 10),
                      margin: const EdgeInsets.symmetric(vertical: 10),
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                          Container(
                              height: 80,
                              width: detailsWidth,
                              child: Column(
                                mainAxisAlignment:
                                    MainAxisAlignment.spaceEvenly,
                                children: [
                                  const Text("runtime"),
                                  Text(
                                    "${(widget.movie.runtime).toInt()} min",
                                    style: const TextStyle(
                                        fontWeight: FontWeight.bold,
                                        fontSize: 16),
                                  )
                                ],
                              ),
                              decoration: const BoxDecoration(
                                  color: Colors.yellow,
                                  borderRadius:
                                      BorderRadius.all(Radius.circular(20)))),
                          Container(
                              height: 80,
                              width: detailsWidth,
                              child: Column(
                                mainAxisAlignment:
                                    MainAxisAlignment.spaceEvenly,
                                children: [
                                  const Text("rating"),
                                  Row(
                                    mainAxisAlignment: MainAxisAlignment.center,
                                    children: const [
                                      Text(
                                        "8",
                                        style: TextStyle(
                                            fontWeight: FontWeight.bold,
                                            fontSize: 20),
                                      ),
                                      Icon(Icons.star)
                                    ],
                                  )
                                ],
                              ),
                              decoration: const BoxDecoration(
                                  color: Colors.yellow,
                                  borderRadius:
                                      BorderRadius.all(Radius.circular(20)))),
                          Container(
                            height: 80,
                            width: detailsWidth,
                            child: Column(
                                mainAxisAlignment:
                                    MainAxisAlignment.spaceEvenly,
                                children: [
                                  const Text("year"),
                                  Row(
                                    mainAxisAlignment: MainAxisAlignment.center,
                                    children: const [
                                      Text(
                                        "2021",
                                        style: TextStyle(
                                            fontWeight: FontWeight.bold,
                                            fontSize: 20),
                                      )
                                    ],
                                  )
                                ],
                              ),
                            decoration: const BoxDecoration(
                                color: Colors.yellow,
                                borderRadius:
                                    BorderRadius.all(Radius.circular(20))),
                          ),
                        ],
                      ),
                    )
                  ],
                )),
            const SizedBox(
              height: 20,
            ),
            Row(
              children: [
                Expanded(
                  child: Text(
                    widget.movie.title,
                    style: Theme.of(context).textTheme.headline6,
                  ),
                )
              ],
            ),
            Container(
              margin: const EdgeInsets.symmetric(vertical: 10),
              height: 1,
              color: Colors.grey[400],
            ),
            Row(
              children: [
                Expanded(
                  child: Text(
                    "Synopsis",
                    style: Theme.of(context).textTheme.subtitle1,
                  ),
                )
              ],
            ),
            const SizedBox(height: 10),
            Row(
              children: [
                Expanded(
                  child: Text(
                    widget.movie.plot,
                    style: Theme.of(context).textTheme.bodyText1,
                  ),
                )
              ],
            )
          ],
        )));
  }
}
```



If we have all the above code intact, we’ll have the app functioning as follows when we run it:

![My Movies app preview](https://lh3.googleusercontent.com/sHmQUThF35O_37tdSBFjngFINrcUcvQmxvKtx9dHGfrBz0cqLtdqZG_JQFJ_W0zT6_UGHx-YLReWToHBJnV4i5F1b7LDC8xdE4Q2sq1KA3nVT61JhsWHtYqRKjiMqeJ3EnSNaYd8)








In summary we have learnt the following from the above example:

- Consuming FaunaDB data with FQL from a Flutter app by using an unofficial Fauna package for Flutter.
- Bloc state management of a Flutter app and the advantages it brings from separating logic and responsibilities within the app to maintaining clean code.
- Setting up a Fauna database, its collections, indexes and keys.
- Requesting data from Fauna by using its Query Language (FQL).

I believe there is so much in store that this stack can offer, Fauna’s custom attribute-based access control (ABAC) for user authentication being one of them with its [expansive support](https://docs.fauna.com/fauna/current/security?utm_source=DevTo&utm_medium=referral&utm_campaign=WritewithFauna_FaunaFlutter_JSinkala) for third party authentication options providing the flexibility on that end.

With the swift scaling capabilities of Fauna and the agility provided in mobile development by Flutter, this stack can possibly be the go to when it comes to creating small to large scale projects on the mobile environment.

Finally I hope that an official support for the Dart language is in the works, or at the least in plan so that developers and the like can explore this stack in building solutions.

I for one will personally keep exploring this combination for a long foreseeable future.