Fluttering On Rails Part 3: Home and Create Pages
Time to create the front end of our mobile application 🤖. Learning Flutter UI by hand is kind of hard, feel free to consult my git repo:
https://github.com/gardnerapp/Fluttering_on_Rails_Front/tree/master/lib
Inside of MyApp add the initialRoute and the Create route:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Rails CRUD API',
theme: ThemeData(
primarySwatch: Colors.deepOrange,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialRoute: '/',
routes: {
'/create': (context) => Create(),
},
home: Home(),
);
}
}
Routing in flutter is a lot like traditional web page routing. In the code above we used the ‘/create’ name and mapped it to the Create() widget. which we will be making shortly ! The ‘/’ initalRoute always takes us back to our Home() widget.
Before we create our Home Widget we need to create some components for the tiles that will reside inside of the ListView. I created a Components folder with a file called navigator_tile.dart (formerly navigator_button.dart) to hold our navigation tiles. Inside of this file add the following code:
import 'package:flutter/material.dart';
class NavigatorTile extends StatelessWidget {
final String title;
final Function onPressed;
final IconData icon;
const NavigatorTile({Key key, this.title, this.onPressed, this.icon}) :
super(key: key);
@override
Widget build(BuildContext context) {
return Card(
color: Colors.black26,
shadowColor: Colors.white70,
child: ListTile(
contentPadding: EdgeInsets.all(10.0),
title: Text(title, style: TextStyle(
color: Colors.white,
fontSize: 20.0,
fontWeight: FontWeight.w600,
),),
onTap: onPressed,
trailing: Icon(icon, size: 30.0, color: Colors.white )
),
);
}
}
Our NavigatorTile widget takes 3 arguments: 1) the Title of the page we will be going to, 2) IconData and 3) A function which will be used to route to various different pages.
OK! Time to create our Home widget (as seen in the beginning). I like to keep my Home widget inside of Main just bellow MyApp.
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Corey\'s Corner"),
),
body: ListView(
scrollDirection: Axis.vertical,
children: <Widget>[
NavigatorTile(
title: "Create",
onPressed: () {
Navigator.pushNamed(context, '/create');
},
icon: Icons.create,
),
NavigatorTile(
title: "Index",
onPressed: () async {
},
icon: Icons.list,
),
NavigatorTile(
title: "Search",
onPressed: () {
},
icon: Icons.library_books,
),
],
),
);
}
}
For now we can pass blank functions to the index and search tiles, we’ll add the proper routing in later. Inside of the Create Navigator tile we call Navigator.pushNamed and pass in the name of the page we want to be taken to. In this case it is the /create route which we specified earlier and mapped to our Create widget.
The next thing we need to do is to make the create page. In the terminal run:
mkdir pages ** ignore just comments ** -> (creates pages directory)
cd pages ** ignore just comments ** -> (enter pages directory)
touch create.dart ** ignore just comments ** -> (create create.dart
file)
open the create.dart file enter:
class Create extends StatefulWidget {
@override
_CreateState createState() => _CreateState();
}
class _CreateState extends State<Create> {
final _formKey = GlobalKey<FormState>();
String name;
String price;
CrudServices crud = CrudServices();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar:
AppBar(title: Text('Create'), leading: PopButton(context: context)),
body: Container(
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
TextFormField(
decoration: textInputDecoration("Name"),
onChanged: (val){
setState(() {
name = val;
});
},
validator: (value){
if (value.isEmpty){
return 'Please enter an Item name';
}
return null;
},
),
SizedBox(height: 30),
TextFormField(
decoration: textInputDecoration("Price"),
onChanged: (val){
setState(() {
price = val;
});
},
validator: (value){
if (value.isEmpty){
return 'Please enter an Item price';
}
return null;
},
),
SizedBox(height: 30),
RaisedButton.icon(onPressed: () async {
if(_formKey.currentState.validate()){
try {
var request = await crud.createItem(name, price);
//item created return home
if (request.statusCode == 200) {
Navigator.pushNamed(context, '/');
}
else{
//TODO push to error page
}
} on Exception catch (e) {
// TODO push to Error Page
}
}
},
elevation: 16.0,
padding: EdgeInsets.all(10.0),
icon: Icon(Icons.open_in_new, color: Colors.white70, size: 30.0,),
label: Text("Submit", style: TextStyle(color: Colors.white, fontSize: 20.0),),
splashColor: Colors.amber,
color: Colors.deepOrange,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
side: BorderSide(color: Colors.orange),
),
)
]))));
}
}
For sake of brevity I am not going to explain how everything works or include some of the more mundane styling components. Just know that we make sure a name and a price are included in the form before we submit it. If the form is valid we await our request, upon a status code of 200 (item created) we return home, else we push to an error page which we will create in the next post.
We wrap our request in a try/catch statement to catch socket errors (Rails API not running locally). Upon encountering an error the navigator will be push an error page which will display the error.
Learn Ruby & React: https://www.youtube.com/channel/UCfd8A1xfzqk7veapUhe8hLQ
My podcast: https://anchor.fm/coreys-corner