30
loading...
This website collects cookies to deliver better user experience
/YOUR_COLLECTION_s
: Creates new content./YOUR_COLLECTION_s
: Gets all the contents./YOUR_COLLECTION_s/:ID
: Gets a single content based on its ID
./YOUR_COLLECTION_s/:ID
: Edits a content/YOUR_COLLECTION_s/:ID
: Deletes a content.Yarn
: Very fast Node package manager. You can install via NPM: npm i yarn -g.flutter CLI
: This command-line tool is used to manage a Flutter project. We can use it to create a Flutter project. Visit https://flutter.dev/docs/get-started/install to install the CLI for your machine.
# Scaffold a Strapi
mkdir strapi_flutter
cd strapi_flutter
. We begin by creating a Strapi project, run the below command:yarn create strapi-app todo-api --quickstart
# OR
npx create-strapi-app todo-api --quickstart
todo-API
with necessary dependencies and start the server by running yarn develop
.http://localhost:1337/admin/auth/register/
will be opened in the browser for you to set up your Strapi administrator credentials.Todo {
name
done
}
name
is the name or text of a todo, e.g., "Buy groceries from the store." The done
is a Boolean field that indicates whether a todo item has been done or not.+ Create new collection
type button. A modal will pop up; on the popup modal, type "todo" in the Display name
input box. The "todo" will be the name of our collection type."Continue"
button and on the following UI that appears on the exact modal. This UI is where we begin to create the fields for the "todo" collection."name."
"+ Add another field"
button, and on the next display, select "Boolean" and type in "done" on the next display that appears. "Finish"
button, the modal will disappear, and we will see the "todo" collection on the page with the fields we just added."Save"
button on the top-right. It will save our "todo"
collection. We will see that a "Todos"
is a content type on the sidebar menu of the dashboard.+ Add New Todos
button on the top-right of the page. A Create an entry
UI will appear. You will see input boxes for all the fields in our Todo model.- `name` -> Go to groceries store
- `done` -> false
Save
button and click on the Publish
button that is now active. This button will make our new data live.+ Add New Todos
and on the next page that appears, add the below data:- `name` -> Go to church
- `done` -> false
Save
and then on Publish
. Next, we open access for all users, both unauthenticated and authenticated users.Settings
item on the sidebar menu, then on the page that loads on the right section, go to the "USERS & PERMISSIONS PLUGIN"
section and click on "Roles," and then on Public
on the right section.Public
page is loaded in this section. Next, scroll down to the Permission
section and check the Select all
box. Next, click on the Save
button on the top-right page, and this now makes our endpoints accessible to the Public.yarn strapi install graphql
todos
collection that looks like the one below.// Todo's type definition
type Todo {
id: ID!
created_at: DateTime!
updated_at: DateTime!
name: String
done: Boolean
published_at: DateTime
}
type TodoConnection {
values: [Todo]
groupBy: TodoGroupBy
aggregate: TodoAggregator
}
type Query {
// gets a single todo via id
todo(id: ID!, publicationState: PublicationState): Todo
// Gets all todos
todos(
sort: String
limit: Int
start: Int
where: JSON
publicationState: PublicationState
): [Todo]
// This gives us more leverage on what to return in the query. E.g, it provides an aggregator that we can use to get the total count of todos data in the backend.
todosConnection(
sort: String
limit: Int
start: Int
where: JSON
): TodoConnection
}
type Mutation {
// creates a new todo
createTodo(input: createTodoInput): createTodoPayload
// Updates a todo
updateTodo(input: updateTodoInput): updateTodoPayload
// deletes a todo
deleteTodo(input: deleteTodoInput): deleteTodoPayload
}
# Write your query or mutation here
query {
todos {
name
done
}
}
# Write your query or mutation here
query {
todo(id: 1) {
name
done
}
}
# Write your query or mutation here
mutation {
createTodo(input: { data: { name: "Clean the house", done: false } }) {
todo {
name
done
}
}
}
# Write your query or mutation here
mutation {
updateTodo(input: { where: { id: 3 }, data: { done: true } }) {
todo {
name
done
}
}
}
# Write your query or mutation here
mutation {
deleteTodo(input: { where: { id: 3 } }) {
todo {
name
done
}
}
}
flutter doctor
to iron them out. After everything has been done, run flutter --version
to make sure the Flutter CLI is available globally in your system.todo-api
folder. From the central folder strapi_flutter
run the below command:flutter create todo_strapi
todo_strapi
that contains a simple demo app that uses Material Components.cd todo_strapi
flutter devices
.flutter run
.main.dart
file in the project. That is the main file in Flutter projects, and it is where the app is being bootstrapped from. Everything in Flutter is a widget.CreateTodo
: This widget is where we will create new todos.TodoList
: This widget will get the list of all the todos in our system.ViewTodo
: This widget is where we will view our todos, edit and delete them.graphql_flutter
: This is a GraphQL client for Flutter that gives us APIs to run queries and mutations conversationally.intl
: This library provides us with DateTime formatting capabilities.pubspec.yaml
file, go to the dependencies
section and add graphql_flutter
and intl
.dependencies:
flutter:
sdk: flutter
intl:
graphql_flutter: ^4.0.0-beta
flutter pub get
in your terminal. Flutter will install the dependencies in your project.mkdir lib/screens
touch lib/screens/ViewTodo.dart lib/screens/CreateTodo.dart lib/GraphQLConfig.dart
GraphQLClient
. This GraphQLClient
will contain a link and cache system.GraphQLClient
source code: The link is a Link over which GraphQL documents will be resolved into a [Response]. The cache is the [GraphQLCache] to use for caching results and optimistic updates.GraphQLConfiguration
class in the GraphQLConfig.dart
file, and this class will have a clientToQuery
method that will return an instance of GraphQLClient
.lib/GraphQLConfig.dart
and paste the below code:import "package:flutter/material.dart";
import "package:graphql_flutter/graphql_flutter.dart";
class GraphQLConfiguration {
static HttpLink httpLink = HttpLink(
'http://10.0.2.2:1337/graphql',
);
static ValueNotifier<GraphQLClient> client = ValueNotifier(
GraphQLClient(
cache: GraphQLCache(),
link: httpLink,
),
);
static ValueNotifier<GraphQLClient> clientToQuery() {
return client;
}
}
static HttpLink httpLink = HttpLink(
'http://10.0.2.2:1337/graphql',
);
GraphQLClient
will resolve documents. See that the link is http://10.0.2.2:1337/graphql
, but why is that? Our Strapi backend runs on http://localhost:1337/graphql
, not on http://10.0.2.2:1337/graphql
.10.0.2.2
, and this URL will forward the HTTP request made to the URL to localhost
. So that's the reason we don't use the localhost
URL.localhost:1337
, then we have to make an HTTP request to 10.0.2.2:1337. The emulator will proxy it to localhost:1337
.cache: GraphQLCache()
makes the GraphQLClient
use its internal cache.GraphQLClient
and stores it in the client
. This is returned in the clientToQuery
static method.lib/main.dart
and paste the below code:import 'package:flutter/material.dart';
import 'dart:math';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:intl/intl.dart';
import 'GraphQLConfig.dart';
import 'screens/CreateTodo.dart';
import 'screens/ViewTodo.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GraphQLProvider(
client: GraphQLConfiguration.clientToQuery(),
child: MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: TodoList(),
));
}
}
main
function is the entry point of Flutter apps. This entry point is where the execution starts. Next, the runApp
starts rendering the widgets in our app. See that we passed it MyApp
widget. This widget is the first widget to render its UI in our app.build
method from either StatelessWidget
or StatefulWidget
to return widgets that will render the UI of our app.StatelessWidget
manages no local state. It is just like a functional component in Reactjs without useState
.StatefulWidget
manages a local state. It is like a functional component in Reactjs with the useState
hook.MyApp
extends the StatelesWidget
because it will be managing no state. In its build method, we have a context argument that is of the BuildContext
instance. BuildContext
is a handle to the location of a widget in the widget tree.GraphQLClient
has Mutation
and Query
widgets. These widgets give us options from where we can make queries and mutations to our GraphQL server. Before making these queries and mutations, we must wrap the Query
and Mutation
widgets in the GraphQLProvider widget.build
method of the MyApp
, we wrapped the MaterialApp
widget in GraphQLProvider
. As a result, the TodoList
widget can now access the Query
and Mutation
widgets.onTap
event registered on them so that when pressed, a ViewTodo
widget screen is opened to view the pressed todo item.FloatingActionButton
that, when clicked it will open the CreateTodo widget screen for us to add new todos. This TodoList
will be a stateful widget. Paste the below code below the MyApp
widget in main.dart
....
class TodoList extends StatefulWidget {
TodoList({Key key}) : super(key: key);
@override
_TodoListState createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
String readTodos = """
query {
todos(sort:"created_at:desc") {
id
name
done
created_at
}
}
""";
var colors = [
Colors.amber,
Colors.green,
Colors.purple,
Colors.orange,
Colors.red,
Colors.yellow
];
Random random = new Random();
var todos = [];
randomColors() {
int randomNumber = random.nextInt(colors.length);
return colors[randomNumber];
}
onChanged(b) {
return true;
}
@override
Widget build(BuildContext context) {
return Query(
options: QueryOptions(
document: gql(readTodos),
pollInterval: Duration(seconds: 0),
),
builder: (QueryResult result,
{VoidCallback refetch, FetchMore fetchMore}) {
if (result.hasException) {
return Text(result.exception.toString());
}
if (result.isLoading) {
return Text('Loading');
}
todos = result.data["todos"];
return Scaffold(
body: Column(children: [
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.fromLTRB(8, 50, 0, 9),
color: Colors.blue,
child: Text(
"Todo",
style: TextStyle(
fontSize: 45,
fontWeight: FontWeight.bold,
color: Colors.white),
)),
Expanded(
child: ListView.builder(
itemCount: todos.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ViewTodo(
id: todos\[index\]["id"],
refresh: () {
refetch();
},
),
),
);
},
child: Container(
margin: const EdgeInsets.fromLTRB(10, 0, 10, 10),
padding: const EdgeInsets.fromLTRB(10, 0, 10, 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(7)),
color: randomColors(),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.fromLTRB(0, 6, 0, 6),
child: Text(
todos\[index\]["name"]
.toString() /*"Go to the grocery store"*/,
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold)),
),
Text(DateFormat("yMMMEd")
.format(DateTime.parse(todos[index]
["created_at"]
.toString()))
.toString()),
],
),
),
Checkbox(
value: todos\[index\]["done"],
onChanged: onChanged)
],
),
));
},
))
]),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CreateTodo(refresh: () {
refetch();
}),
),
);
},
tooltip: 'Add new todo',
child: Icon(Icons.add),
),
);
});
}
}
TodoList
uses the createState
method to create its mutatable State at the _TodoListState
, and this _TodoListState
renders the UI widget for the TodoList
.State
class are:- The logic and internal state for a [StatefulWidget].
- The State is information that (1) can be read synchronously when the widget is built and (2) might change during the widget's lifetime. It is the responsibility of the widget implementer to ensure that the [State] is promptly notified when such state changes, using [State.setState].
_TodoListState
widget, we start by defining the query to read the todos in the readTodos
String variable. We have an array of colors, and we used this to color the background of our todos list widget randomly.todos
variables will hold the todos list fetched from our backend. The randomColors
is the method that will randomly return a color for each todo widget.Query
widget wraps the whole widget tree. This is done to reference the returned todos and a vital function refetch
we can use to refresh our todos list when a change occurs.Query
widget uses the document
method in its options
object to query for the todos list. It does this by calling the gql
method with the readTodos variable. The result of this query is returned in the builder
function's result
argument.todos
variable:todos = result.data["todos"];
Scaffold(...)
widget. We use the todos
variable to render each result there in the ListView.builder
, which builds the result in a list with the custom UI we set.GestureDetector
widget is set on each Todo list to put an onTap
event on them.child: ListView.builder(
itemCount: todos.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ViewTodo(
id: todos\[index\]["id"],
refresh: () {
refetch();
},
),
),
);
},
...
ViewTodo
widget screen is launched. We passed to it the id of the Todo and a refresh function. This refresh function calls the refetch
function returned by the Query
widget. This is done to refresh the TodoList
view from the ViewTodo
widget when a change to the Todo is made.FloatingActionButton
:...
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CreateTodo(refresh: () {
refetch();
}),
),
);
},
tooltip: 'Add new todo',
child: Icon(Icons.add),
),
...
CreateTodo
widget when it is clicked. Let's look at the ViewTodo
widget.lib/screens/ViewTodo.dart
:import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import '../GraphQLConfig.dart';
String readTodo = """
query(\$id: ID!) {
todo(id: \$id) {
name
done
}
}
""";
String updateTodo = """
mutation(\$id: ID!, \$done: Boolean, \$name: String) {
updateTodo(input: { where: { id: \$id }, data: { done: \$done, name: \$name } }) {
todo {
name
done
}
}
}
""";
String deleteTodo = """
mutation(\$id: ID!) {
deleteTodo(input: { where: { id: \$id } }) {
todo {
name
done
}
}
}
""";
class ViewTodo extends StatefulWidget {
final id;
final refresh;
ViewTodo({Key key, @required this.id, this.refresh}) : super(key: key);
@override
ViewTodoState createState() => ViewTodoState(id: id, refresh: this.refresh);
}
class ViewTodoState extends State<ViewTodo> {
final id;
final refresh;
ViewTodoState({Key key, @required this.id, this.refresh});
var editMode = false;
var myController;
bool done;
@override
Widget build(BuildContext context) {
return GraphQLProvider(
client: GraphQLConfiguration.clientToQuery(),
child: Query(
options: QueryOptions(
document: gql(readTodo),
variables: {'id': id},
pollInterval: Duration(seconds: 0),
),
builder: (QueryResult result,
{VoidCallback refetch, FetchMore fetchMore}) {
if (result.hasException) {
return Text(result.exception.toString());
}
if (result.isLoading) {
return Text('Loading');
}
// it can be either Map or List
var todo = result.data["todo"];
done = todo["done"];
myController =
TextEditingController(text: todo["name"].toString());
return Scaffold(
appBar: AppBar(
elevation: 0,
automaticallyImplyLeading: false,
backgroundColor: Colors.blue,
flexibleSpace: SafeArea(
child: Container(
padding: EdgeInsets.only(
right: 16, top: 4, bottom: 4, left: 0),
child: Row(children: <Widget>[
IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Icon(
Icons.arrow_back,
color: Colors.white,
),
),
SizedBox(
width: 20,
),
Text(
"View Todo",
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
color: Colors.white),
),
])))),
body: Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(9),
),
width: double.infinity,
child: editMode
? Column(
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(0, 0, 0, 4),
child: Text("Todo:",
textAlign: TextAlign.left,
style: TextStyle(
color: Colors.black,
fontSize: 20,
))),
TextField(
controller: myController,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Add todo'),
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
padding:
const EdgeInsets.fromLTRB(0, 0, 0, 4),
child: Text("Done:",
textAlign: TextAlign.left,
style: TextStyle(
color: Colors.black,
fontSize: 20,
))),
StatefulBuilder(builder:
(BuildContext context,
StateSetter setState) {
return new Checkbox(
value: done,
onChanged: (bool value) {
print("done:" + done.toString());
setState(() {
done = value;
});
},
);
}),
])
],
)
: Column(
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(0, 0, 0, 4),
child: Text("Todo:",
textAlign: TextAlign.left,
style: TextStyle(
color: Colors.black,
fontSize: 20,
)),
),
Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(0, 0, 0, 4),
child: Text(todo["name"].toString(),
textAlign: TextAlign.left,
style: TextStyle(
color: Colors.black,
fontSize: 30,
fontWeight: FontWeight.bold))),
Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(0, 10, 0, 4),
child: Text("Done:",
textAlign: TextAlign.left,
style: TextStyle(
color: Colors.black,
fontSize: 20,
)),
),
Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(0, 0, 0, 4),
child: Text(todo["done"].toString(),
textAlign: TextAlign.left,
style: TextStyle(
color: Colors.black,
fontSize: 30,
fontWeight: FontWeight.bold)),
)
],
),
),
floatingActionButton: !editMode
? Mutation(
options: MutationOptions(
document: gql(deleteTodo),
update: (GraphQLDataProxy cache, QueryResult result) {
return cache;
},
onCompleted: (dynamic resultData) {
print(resultData);
refresh();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Done.')));
Navigator.pop(context);
},
),
builder: (
RunMutation runMutation,
QueryResult result,
) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: EdgeInsets.fromLTRB(0, 0, 0, 5),
child: FloatingActionButton(
mini: true,
heroTag: null,
child: Icon(Icons.delete),
onPressed: () {
runMutation({'id': id});
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content:
Text('Deleting todo...')));
},
)),
FloatingActionButton(
onPressed: () {
setState(() {
editMode = true;
});
},
tooltip: 'Edit todo',
child: Icon(Icons.edit),
)
]));
})
: Mutation(
options: MutationOptions(
document: gql(updateTodo),
update: (GraphQLDataProxy cache, QueryResult result) {
return cache;
},
onCompleted: (dynamic resultData) {
print(resultData);
refresh();
refetch();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Done.')));
},
),
builder: (
RunMutation runMutation,
QueryResult result,
) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: EdgeInsets.fromLTRB(0, 0, 0, 5),
child: FloatingActionButton(
mini: true,
heroTag: null,
child: Icon(Icons.cancel),
onPressed: () {
setState(() {
editMode = false;
});
},
)),
FloatingActionButton(
heroTag: null,
child: Icon(Icons.save),
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Updating todo...')));
runMutation({
'id': id,
'name': myController.text,
'done': done
});
setState(() {
editMode = false;
});
},
)
]));
}),
);
}));
}
}
readTodo
, updateTodo
, and deleteTodo
. The readTodo
is a query string to return a todo by its id. The updateTodo
is a mutation to update a todo using its id
with new done
and name
values. The deleteTodo
is also a mutation that deletes a todo.ViewTodo
is a stateful widget and manages its State in the ViewTodoState
widget. Every variable inside the ViewTodoState
widget is a state variable that can be updated during the widget's lifetime.ViewTodoState
widget, see that we have an editMode
boolean variable. This variable sets the edit mode of the widget.myController
is a text controller for a text field when editing the Todo in an edit mode. We use it to get the value typed in a TextField.bool done;
is used to hold the done
field of the todo.build
method, we enclosed the whole widget in the tree with the Query
widget. It calls the readTodo
on start-up and renders the name and done fields of the Todo in the UI.editMode
is active and render text field and the checkbox to edit the Todo. If there is no edit mode, the todo details are rendered on Text widgets.editMode
to render FloatingActionButtons
based on the current model.save
and cancel
FloatingActionButtons
will show. The save
FloatingActionButton
will save the edited Todo. It will collect the name
value from TextField and collect the done
value from the State of the CheckBox. Then, it will call the runMutation
with the values.onCompleted
function of the Mutation
object enclosing the edit section of the save
and cancel
FloatingActionButton
. refresh
method to refresh the list of todos in the TodoList
and the refetch
method from the Query
widget to refresh this ViewTodo
widget because the current Todo has been modified....
onCompleted: (dynamic resultData) {
print(resultData);
refresh();
refetch();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Done.')));
},
...
edit
and delete
FBs are shown. The edit
FB, when clicked, sets the editMode
State to true
. The delete
FB, when clicked, sends the deleteTodo
to delete the current Todo. onCompleted
function of the Mutation
widget that enclosed it, we called the refetch
method and popped the ViewTodo
widget off the screen because it was deleted and no longer available....
onCompleted: (dynamic resultData) {
print(resultData);
refresh();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Done.')));
Navigator.pop(context);
},
...
CreateTodo
screen.MaterialButton
that will run a mutation when clicked.lib/screens/CreateTodo.dart
:import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import './../GraphQLConfig.dart';
String addTodo = """
mutation(\$name: String, \$done: Boolean) {
createTodo(input: { data: { name: \$name, done: \$done } }) {
todo {
name
done
}
}
}
""";
class CreateTodo extends StatelessWidget {
final myController = TextEditingController();
final refresh;
CreateTodo({Key key, this.refresh}) : super(key: key);
@override
Widget build(BuildContext context) {
return GraphQLProvider(
client: GraphQLConfiguration.clientToQuery(),
child: Mutation(
options: MutationOptions(
document:
gql(addTodo),
update: (GraphQLDataProxy cache, QueryResult result) {
return cache;
},
onCompleted: (dynamic resultData) {
refresh();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('New todo added.')));
Navigator.pop(context);
},
),
builder: (
RunMutation runMutation,
QueryResult result,
) {
return Scaffold(
appBar: AppBar(
title: Text("Create Todo"),
),
body: Column(children: [
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.fromLTRB(10, 50, 10, 9),
child: TextField(
controller: myController,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Add todo'),
)),
Row(children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(10),
child: MaterialButton(
onPressed: () {
runMutation({
'name': myController.text,
'done': false
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Adding new todo...')));
},
color: Colors.blue,
padding: const EdgeInsets.all(17),
child: Text(
"Add",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 20),
),
)))
])
]));
}));
}
}
createTodo
mutation set. This mutation string will create a new todo in our Strapi.CreateTodo
is a stateless widget, and it manages no state. The constructor accepts the refresh function passed to it and stores in it the refresh
variable.myController
is a TextEditingController
used to manipulate TextFields.GraphQLProvider
and Mutation
widgets. The document
function will run the gql(createTodo)
function call when the runMutation
argument in its builder
function is called.myController
is set to the TextField. This will enable us to use the myController
to get the value of the TextField.MaterialButton
has an onPressed
event registered to it. Its handler will be called when the button is pressed. This will retrieve the value in the TextField using the myController
. It will call the runMutation
function passing in the value in the TextField. This will run the createTodo
mutation thereby creating a new todo in our Strapi backend.onCompleted
function will be called when the mutation completes:MaterialButton
has an onPressed
event registered to it. Its handler will be called when the button is pressed. This will retrieve the value in the TextField using the myController
. createTodo
mutation, thereby creating a new todo in our Strapi backend.onCompleted
function will be called when the mutation completes:...
onCompleted: (dynamic resultData) {
refresh();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('New todo added.')));
Navigator.pop(context);
},
...
refresh
function passed to the CreateTodo
widget from the TodoList
widget is called, so the todos list in the TodoList
widget is updated to display our newly added todo item.