Learn More

Try out the world’s first micro-repo!

Learn More

flutter.py | Kings of both -ends

flutter.py | Kings of both -ends

Today, we’re going to integrate the Flask-RESTful backend, a Python extension, with Flutter as our frontend, taking advantage of a very simple API.

In short, we’re uniting the king of FRONTEND and the king of BACKEND.

🎨 Flutter & Flask-RESTful

Flutter is a UI toolkit to develop cross-platform and performant apps. It’s highly flexible and developed by Google.

Flask-RESTful is an extension for Flask that adds support for quickly building REST APIs. It’s powered by Python.

🕸 REST API

REST API is an application programming interface (API) based on the REST architectural style. It includes methods like GET, POST, DELETE, etc., for sending and fetching data. All HTTP methods can be used with REST APIs.

🧑🏻‍💻 Writing our first REST API

So, we’ll be using the flask-restful library, with is powered by Python. Hence, the first step will be installing the library— we all know that pip is the manager that lets you install various packages in Python.

pip install flask-restful

Mac OS: If pip isnt working for you and you’ve installed the latest Python version, then pip3 will be your call. Same for the python to python3 keyword.

We’ll start off very simply by creating the file app.py and writing a very simple hello world API response.

import flask
from flask_restful import Resource, Api

app = flask.Flask(__name__)
api = Api(app)


class HelloWorld(Resource):
def get(self):
return {
'hello': 'world',
}


api.add_resource(HelloWorld, '/')

if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=8080)

Save this code

Run the command:

python app.py

And open http://localhost:8080

We’re using extension JSON Viewer for this look.

A little deeper

Now, let’s make this API response a little more complex so we can showcase it on our Flutter frontend.

import flask
from flask_restful import Resource, Api

app = flask.Flask(__name__)
api = Api(app)


class HelloWorld(Resource):
def get(self):

data = [
'first',
'API',
'Response',
'with',
'random List',
'python',
]

return {
'data': data,
}


api.add_resource(HelloWorld, '/')

if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=8080)

Save this code

Now, when you open the localhost it's going to look something like this:

💻 Integration in Flutter

I’m going to use flutter_bloc to handle the REST APIs, followed by cubits and states. So, the project structure is going to be like this:

Model

We’ll start with a very simple model class that holds just a List<String>

import 'dart:convert';

class Data {
final List<String> words;
Data({
required this.words,
});

Data copyWith({
List<String>? words,
}) {
return Data(
words: words ?? this.words,
);
}

Map<String, dynamic> toMap() {
return <String, dynamic>{
'words': words,
};
}

factory Data.fromMap(Map<String, dynamic> map) {
return Data(
words: List<String>.from((map['words'] as List)),
);
}

String toJson() => json.encode(toMap());

factory Data.fromJson(String source) =>
Data.fromMap(json.decode(source) as Map<String, dynamic>);

@override
String toString() => 'Data(words: $words)';

@override
int get hashCode => words.hashCode;
}

Save this code

data_provider.dart

Then, we’ll request the data from the API in the data_provider layer.

You may use dio here as well, which is my preference.

part of 'cubit.dart';

class DataDataProvider {
static Future<Data> fetch() async {
try {
final request = await http.get(Uri.parse('http://localhost:8080'));

return Data.fromJson(request.body);
} catch (e) {
throw Exception("Internal Server Error");
}
}
}

Save this code

repository.dart

Next, just pass the data from API to the cubits:

part of 'cubit.dart';

class DataRepository {
Future<Data> fetch() => DataDataProvider.fetch();
}

Save this code

state.dart

We’re handling a few different states here:

  • Loading state
  • Success state
  • Failure state
part of 'cubit.dart';

@immutable
class DataState extends Equatable {
final Data? data;
final String? message;

const DataState({
this.data,
this.message,
});

@override
List<Object?> get props => [
data,
message,
];
}

@immutable
class DataDefault extends DataState {}

@immutable
class DataFetchLoading extends DataState {
const DataFetchLoading() : super();
}

@immutable
class DataFetchSuccess extends DataState {
const DataFetchSuccess({Data? data}) : super(data: data);
}

@immutable
class DataFetchFailed extends DataState {
const DataFetchFailed({String? message}) : super(message: message);
}

Save this code

cubit.dart

Finally, we’re omitting states based on if we have the data or not.

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:http/http.dart' as http;

import 'package:jugaad/models/data.dart';

part 'data_provider.dart';
part 'repository.dart';
part 'state.dart';

class DataCubit extends Cubit<DataState> {
static DataCubit cubit(BuildContext context, [bool listen = false]) =>
BlocProvider.of<DataCubit>(context, listen: listen);

DataCubit() : super(DataDefault());

final repo = DataRepository();

Future<void> fetch() async {
emit(const DataFetchLoading());
try {
final data = await repo.fetch();

emit(DataFetchSuccess(data: data));
} catch (e) {
emit(DataFetchFailed(message: e.toString()));
}
}
}

Save this code

Now let’s handle the frontend using BlocBuilder. The final product will be something like this:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:jugaad/cubits/data/cubit.dart';

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

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

@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => DataCubit()),
],
child: const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'flutter.py',
home: DataScreen(),
),
);
}
}

class DataScreen extends StatefulWidget {
const DataScreen({Key? key}) : super(key: key);

@override
State<DataScreen> createState() => _DataScreenState();
}

class _DataScreenState extends State<DataScreen> {
@override
void initState() {
super.initState();

DataCubit.cubit(context).fetch();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocBuilder<DataCubit, DataState>(
builder: (context, state) {
// loading
if (state is DataFetchLoading) {
return const Center(
child: CircularProgressIndicator(),
);
}

// success
else if (state is DataFetchSuccess) {
return ListView(
children: state.data!.words
.map(
(word) => ListTile(
title: Text(word),
),
)
.toList(),
);
}

// failure
else if (state is DataFetchFailed) {
return Center(
child: Text(state.message!),
);
}

// something unexpected
return const Center(
child: Text('Something went wrong'),
);
},
),
);
}
}

Save this code

And …… 🥁🥁🥁

Data from the Flask API

🔥 Complexity++

For the sake of more understanding, I’ll make our API response a bit more complex and then change the data.dart model class accordingly to fill up the UI.

API response

import flask
from flask_restful import Resource, Api

app = flask.Flask(__name__)
api = Api(app)


class HelloWorld(Resource):
def get(self):

data = [
{
'word': 'cat',
'type': 'animal',
},
{
'word': 'football',
'type': 'sports',
},
{
'word': 'rice',
'type': 'food',
},
]

return {
'data': data,
}


api.add_resource(HelloWorld, '/')

if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=8080)

Save this code

Data.dart

import 'dart:convert';

class Data {
final String word;
final String type;
Data({
required this.word,
required this.type,
});

Data copyWith({
String? word,
String? type,
}) {
return Data(
word: word ?? this.word,
type: type ?? this.type,
);
}

Map<String, dynamic> toMap() {
return <String, dynamic>{
'word': word,
'type': type,
};
}

factory Data.fromMap(Map<String, dynamic> map) {
return Data(
word: map['word'] as String,
type: map['type'] as String,
);
}

String toJson() => json.encode(toMap());

factory Data.fromJson(String source) =>
Data.fromMap(json.decode(source) as Map<String, dynamic>);

@override
String toString() => 'Data(word: $word, type: $type)';

@override
int get hashCode => word.hashCode ^ type.hashCode;

@override
bool operator ==(covariant Data other) {
if (identical(this, other)) return true;

return other.word == word && other.type == type;
}
}

Save this code

And the final look on our UI will be something like this:

That’s all, folks! I hope you’ve learned something today, and I hope some genius reads this article and comes up with an actual dart package named flutter.py that handles all the fuss with ease!

People always ask questions like, “What kind of backend we can use with Flutter?” “Can we use Python as the backend?” “Can we use Node.js?” and so on.

Flutter is just a UI toolkit, which means you can attach anything in the backend. We used Flask-RESTful for this purpose, but you can use Django, Node.js or any of your favorite backends.

So, keep exploring and keep creating awesome stuff!

Thanks, y’all and #happyfluttering 💙

Interested in becoming a Pieces Content Partner?

Learn More

Get our latest blog posts and product updates by signing up for our monthly newsletter! 

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Table of Contents

Flutter

Python

More from Pieces