How to Build a serverless application with AWS and Flutter

I love creating and writing about the creation of software.
Search for a command to run...

I love creating and writing about the creation of software.
No comments yet. Be the first to comment.
This series would cover creating a serverless API and use it as a backend for web and mobile applications
Introduction Hi, in the article, we'll be creating a serverless rest api using AWS(Amazon Web Services). I'll link off to other articles describing concepts which are beyond the scope of this article. So let's get down to it. What is Serverless ? 🤔...
Watch the complete Video on Youtube https://www.youtube.com/watch?v=kKX-8L_R2XM Prerequisites Before proceeding make sure you have these dependencies Docker Python 3.11 and above AWS CLI Full access to a foundation model. For this workshop, we'll...

Introduction In a previous workshop, Building event-driven applications with AWS sam and python we built an e-commerce ordering service using AWS SAM, API Gateway, Lambda and Python. Here's the solutions architecture. In that workshop, whenever a ne...

Github Repository https://github.com/EducloudHQ/introduction_to_gen_ai Skill up with serverless on Educloud https://www.educloud.academy/content/da99ad07-7efa-41e7-ba50-b18e6b89e10d This post is an introductory lesson to building AI-enhanced serverle...

Introduction Serverless computing is a cloud-based approach to application development that eliminates the need for provisioning and managing servers. Instead, developers focus on writing code that is triggered by events such as HTTP requests or data...

https://github.com/trey-rosius/sam_stepfunctions Hey!!! How you doing? In this post, we’ll look at how to build a workflow, using SAM as IaC, Appsync, and python. Prerequisites Install AWS Cli (https://docs.aws.amazon.com/cli/latest/userguide/cli-c...

This article is the third in a series of "Build serverless apps with AWS".
The first article covered how to create an API with Lambda, dynamo db,api-gateway.
The second article covered creating a Jamstack application with vue/nuxt to consume our API. The App is hosted on netlify.
https://eloquent-mestorf-134416.netlify.app/
The whole concept idea was to build a serverless web and mobile app.
In this article, we will be creating a flutter app based on the API we created in the first part of this series. So I recommend you check it out, in order to get a complete picture of the system we are about to build.
We are fetching data over the internet. Here's my get request. But I won't show you the API.[
{
"total": "$26.40",
"order_date": "12 jan 2021, 08:28pm",
"order_no": "#6758",
"ordered_by": "https://rosius.s3.us-east-2.amazonaws.com/cropped_rosius.png",
"items": [
{
"name": "Vegetable Mixups",
"pic": "https://rosius.s3.us-east-2.amazonaws.com/meal1+(1).jpg",
"price": "$10.20",
"desc": "Vegetable Fritters with Egg",
"qty": 5
},
{
"name": "Chicken Mixed Salad",
"pic": "https://rosius.s3.us-east-2.amazonaws.com/meal4+(1).jpeg",
"price": "$16.20",
"desc": "Roasted Chicken, mixed with salad",
"qty": 2
}
]
},
{
.....
....
},
{
......
.....
}
]
dependencies:
http: <latest_version>
Additionally, in the AndroidManifest.xml file, add internet permission. Since we are fetching the data over the internet <!-- Required to fetch data from the internet. -->
<uses-permission android:name="android.permission.INTERNET" />
Most of the icons used in the app are svg. Therefore we'll need a package to display those icons properly.flutter_svg: ^0.21.0+1
So your dependencies should be
dependencies:
http: <latest_version>
flutter_svg: ^0.21.0+1
Run Pub get and everything should be fine.
Here's how our custom dart objects for Order and Item looks like class Order{
final String orderNo;
final String orderDate;
final String total;
final String orderedBy;
final List<Item> items;
Order({ @required this.orderNo, @required this.orderDate,
@required this.total, @required this.orderedBy,@required this.items});
factory Order.fromJson(Map<String,dynamic> json){
var parsedItems = json['items'].cast<Map<String, dynamic>>();
return Order(orderNo: json['order_no'],
orderDate: json['order_date'],
total: json['total'], orderedBy: json['ordered_by'],
items: parsedItems.map<Item>((json) => Item.fromJson(json)).toList()
);
}
}
class Item{
final String name;
final String pic;
final String price;
final String description;
final int qty;
Item({@required this.name, @required this.pic, @required this.price,
@required this.description, @required this.qty});
factory Item.fromJson(Map<String,dynamic> json){
return Item(name: json['name'], pic: json['pic'], price: json['price'],
description: json['desc'], qty: json['qty']);
}
}
Each of the above classes includes a factory constructor that creates an Order and Item from JSON.
Since we are getting the list of data over the internet, it's always best to do these tasks in a background thread. This prevents our application from freezing momentarily when getting the data. Here's where the compute()function comes in handy
The compute() function runs expensive functions in a background isolate and returns the result. In this case, run the parseOrders() function in the background.
final String getOrdersUrl = "https://suczbh984e.execute-api.us-east-2.amazonaws.com/dev/orders";
// A function that converts a response body into a List<Order>.
List<Order> parseOrders(String responseBody) {
final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<Order>((json) => Order.fromJson(json)).toList();
}
Future<List<Order>> fetchOrders(http.Client client) async {
final response = await client
.get(Uri.parse(getOrdersUrl),headers: {
"x-api-key":Config.API_KEY
});
return compute(parseOrders,response.body);
}
Be sure to replace the getOrdersUrl with yours and also the Config.API_KEY with yours.
Here's how the code looks like .
class OrderItem extends StatelessWidget {
OrderItem(this.order);
final Order order;
@override
Widget build(BuildContext context) {
return Card(
elevation: 10,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10,vertical: 10),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text("Order",style: TextStyle(fontWeight: FontWeight.bold,color: Theme.of(context).primaryColor)),
Text(order.orderNo,style: TextStyle(fontWeight: FontWeight.bold,color: Theme.of(context).primaryColor)),
],
),
Text(order.orderDate,style: TextStyle(fontSize: 12),),
],
),
),
ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(100)),
child: Image.network(order.orderedBy,height: 30,width: 30,),
)
],
),
Divider(),
Container(
padding: EdgeInsets.only(top: 10,bottom: 10),
child: ListView.separated(
separatorBuilder: (context,index){
return Container(margin: EdgeInsets.only(left: 60),
child: Divider(color: Theme.of(context).accentColor,));
},
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemBuilder: (context,index){
List<Item> listItem = order.items;
return Container(
child: Row(
children: [
Container(
padding:EdgeInsets.all(10),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(100)),
child: SizedBox(
child: Image.network(listItem[index].pic,fit: BoxFit.cover,height: 40,width: 40,)),
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(listItem[index].name,style: TextStyle(fontSize: 12),),
Text(listItem[index].description,style: TextStyle(color: Colors.grey),),
Container(
padding:EdgeInsets.only(top: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween ,
children: [
Text(listItem[index].price,style: TextStyle(fontWeight: FontWeight.bold,color: Theme.of(context).accentColor),),
Row(
children: [
Text("Qty:"),
Text(listItem[index].qty.toString()),
],
),
],
),
)
],
),
)
],
),
);
},itemCount: order.items.length,),
)
],),
),
);
}
}
The initState() method is called exactly once and then never again. If you want to have the option of reloading the API in response to an InheritedWidget changing, put the call into the didChangeDependencies() method.
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Future<List<Order>> futureOrders;
int _selectedIndex = 0;
@override
void initState() {
// TODO: implement initState
super.initState();
futureOrders = fetchOrders(http.Client());
}
You must provide two parameters:
Because fetchOrders can only return non-null values, the function should throw an exception even in the case of a “404 Not Found” server response. Throwing an exception sets the snapshot.hasError to true which can be used to display an error message.
Otherwise, the spinner will be displayed.
FutureBuilder<List<Order>>(
future: futureOrders,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Order> orderLists = snapshot.data;
return ListView.builder(itemBuilder: (context,index){
return OrderItem(orderLists[index]);
},itemCount: snapshot.data.length,);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return Container(
height: 40,
width: 40,
child: Center(child: CircularProgressIndicator()));
},
)
Here's the complete code for home_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:food_dashboard/model/item.dart';
import 'package:food_dashboard/model/order.dart';
import 'package:food_dashboard/order_item.dart';
import 'package:http/http.dart' as http;
import 'api.dart';
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Future<List<Order>> futureOrders;
int _selectedIndex = 0;
@override
void initState() {
// TODO: implement initState
super.initState();
futureOrders = fetchOrders(http.Client());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Food Dashboard"),centerTitle: true,),
body: Row(
children: [
NavigationRail(
// minWidth: 40.0,
// groupAlignment: 0.5,
groupAlignment: 1,
trailing: IconButton(
icon: Icon(Icons.logout),
),
selectedIndex: _selectedIndex,
onDestinationSelected: (int index) {
setState(() {
_selectedIndex = index;
});
},
labelType: NavigationRailLabelType.selected,
selectedLabelTextStyle: TextStyle(color: Theme.of(context).primaryColor,fontWeight: FontWeight.bold),
destinations: [
NavigationRailDestination(
label: Text(""),
icon: Padding(
padding: EdgeInsets.all(5),
child:SvgPicture.asset(
"assets/dashboard.svg",
height: 20,
width: 20,
fit: BoxFit.cover,
// color: color,
),
),
selectedIcon: Padding(
padding: EdgeInsets.all(5),
child:SvgPicture.asset(
"assets/dashboard.svg",
height: 20,
width: 20,
fit: BoxFit.cover,
color: Theme.of(context).primaryColor,
),
)),
NavigationRailDestination(
label: Text(""),
icon: Padding(
padding: EdgeInsets.all(5),
child:SvgPicture.asset(
"assets/orders.svg",
height: 20,
width: 20,
fit: BoxFit.cover,
// color: color,
),
),
selectedIcon: Padding(
padding: EdgeInsets.all(5),
child:SvgPicture.asset(
"assets/orders.svg",
height: 20,
width: 20,
fit: BoxFit.cover,
color: Theme.of(context).primaryColor,
),
)),
NavigationRailDestination(
label: Text(""),
icon: Padding(
padding: EdgeInsets.all(5),
child:SvgPicture.asset(
"assets/home.svg",
height: 20,
width: 20,
fit: BoxFit.cover,
// color: color,
),
),
selectedIcon: Padding(
padding: EdgeInsets.all(5),
child:SvgPicture.asset(
"assets/home.svg",
height: 20,
width: 20,
fit: BoxFit.cover,
color: Theme.of(context).primaryColor,
),
)),
NavigationRailDestination(
label: Text(""),
icon: Padding(
padding: EdgeInsets.all(5),
child:SvgPicture.asset(
"assets/messages.svg",
height: 20,
width: 20,
fit: BoxFit.cover,
color: Colors.black,
),
),
selectedIcon: Padding(
padding: EdgeInsets.all(5),
child:SvgPicture.asset(
"assets/messages.svg",
height: 20,
width: 20,
fit: BoxFit.cover,
color: Theme.of(context).primaryColor,
),
)),
NavigationRailDestination(
label: Text(""),
icon: Padding(
padding: EdgeInsets.all(5),
child:SvgPicture.asset(
"assets/settings.svg",
height: 20,
width: 20,
fit: BoxFit.cover,
color: Colors.black,
),
),
selectedIcon: Padding(
padding: EdgeInsets.all(5),
child:SvgPicture.asset(
"assets/settings.svg",
height: 30,
width: 30,
fit: BoxFit.cover,
color: Theme.of(context).primaryColor,
),
)),
],
),
VerticalDivider(thickness: 1, width: 1),
Expanded(child: FutureBuilder<List<Order>>(
future: futureOrders,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Order> orderLists = snapshot.data;
return ListView.builder(itemBuilder: (context,index){
return OrderItem(orderLists[index]);
},itemCount: snapshot.data.length,);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return Container(
height: 40,
width: 40,
child: Center(child: CircularProgressIndicator()));
},
))
],
)
);
}
}
Generate mocks using
flutter pub run build_runner build
Change the URL and apikey to yours and run the test using
flutter test test/fetch_orders_test.dart
Hopefully, all tests pass.
If tests fail, then get down to debugging ...😂
Happy Coding ✌🏿
I also have tutorials on the basics of dynamoDB.
https://phatrabbitapps.com/dynamodbdemistyfied-chapter-1.