Your app collects data. Mountains of it. But raw numbers mean nothing to users. They want to see trends. They want visual context. They want charts.
If you build Flutter apps, you know this problem. You also know that building charts from scratch feels like painting the Sistine Chapel when you just need to hang a picture.
Google created charts_flutter to solve this. It is the official charting library for Flutter. It gives you bar charts, pie charts, line charts, and more. All with smooth animations. All with clean Material Design styling. No fuss required.
This guide walks you through building real charts in Flutter. We will skip the theory. We will focus on what you do first, second, and third to get charts working in your app.
WHY CHARTS_FLUTTER OVER THE ALTERNATIVES
Flutter has many charting libraries. fl_chart offers customization. BezierChart provides smooth curves. MPFlutterChart gives you bubble charts and scatter plots.
But charts_flutter remains the strongest choice for most projects. It comes from Google, the creator of Flutter itself. This matters because the library follows Flutter conventions without awkwardness. The charts look native to Material Design. The code reads like Flutter code, not like someone ported a JavaScript library.
The library includes touch interactivity, animations by default, and a straightforward API. You spend less time fighting the library and more time solving your actual problem.
STARTING YOUR FIRST CHART PROJECT
Create a new Flutter project with this command:
flutter create my_charts_app
Open your project in your editor. Find the pubspec.yaml file. Under dependencies, add this line:
charts_flutter: ^0.11.0
Your dependencies section should look like this:
dependencies:
flutter:
sdk: flutter
charts_flutter: ^0.11.0
Run flutter pub get to fetch the package.
Now open main.dart. Delete all the default code. Replace it with this basic structure:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text('Chart goes here'),
),
);
}
}
This gives you a clean starting point. The debugShowCheckedModeBanner: false line removes the debug banner. The HomePage class will hold your chart.
CREATING YOUR DATA MODEL
Charts need data. But charts need data in a specific format. You define this format with a model.
Create a new file in your lib folder called developer_series.dart:
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter/foundation.dart';
class DeveloperSeries {
final int year;
final int developers;
final charts.Color barColor;
DeveloperSeries({
required this.year,
required this.developers,
required this.barColor,
});
}
This model describes what data your chart expects. Every bar in your chart will be one DeveloperSeries object. Each object has a year, a count of developers, and a color.
The @required keyword ensures you cannot create a DeveloperSeries without all three properties. This prevents bugs where you forget to pass data.
GENERATING SAMPLE DATA
Now add sample data to your HomePage. Update the homepage file:
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:my_charts_app/developer_series.dart';
class HomePage extends StatelessWidget {
final List<DeveloperSeries> data = [
DeveloperSeries(
year: 2017,
developers: 40000,
barColor: charts.ColorUtil.fromDartColor(Colors.green),
),
DeveloperSeries(
year: 2018,
developers: 50000,
barColor: charts.ColorUtil.fromDartColor(Colors.green),
),
DeveloperSeries(
year: 2019,
developers: 60000,
barColor: charts.ColorUtil.fromDartColor(Colors.green),
),
DeveloperSeries(
year: 2020,
developers: 55000,
barColor: charts.ColorUtil.fromDartColor(Colors.green),
),
DeveloperSeries(
year: 2021,
developers: 75000,
barColor: charts.ColorUtil.fromDartColor(Colors.green),
),
];
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text('Data loaded'),
),
);
}
}
This creates a list of five years of developer growth data. The colors are green. You would replace these numbers with your actual data. You could fetch data from an API, pull it from a database, or load it from JSON. The model stays the same.
BUILDING YOUR FIRST CHART WIDGET
Create a new file called developer_chart.dart:
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:my_charts_app/developer_series.dart';
class DeveloperChart extends StatelessWidget {
final List<DeveloperSeries> data;
const DeveloperChart({required this.data, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
List<charts.Series<DeveloperSeries, String>> series = [
charts.Series<DeveloperSeries, String>(
id: "developers",
data: data,
domainFn: (DeveloperSeries series, _) => series.year.toString(),
measureFn: (DeveloperSeries series, _) => series.developers,
colorFn: (DeveloperSeries series, _) => series.barColor,
),
];
return Container(
height: 300,
padding: const EdgeInsets.all(25),
child: Card(
child: Padding(
padding: const EdgeInsets.all(9.0),
child: Column(
children: <Widget>[
Text(
"Developer Community Growth",
style: Theme.of(context).textTheme.bodyMedium,
),
Expanded(
child: charts.BarChart(
series,
animate: true,
),
),
],
),
),
),
);
}
}
This code does several things at once. Let me break it down.
The series list tells the chart how to interpret your data. The domainFn function specifies what goes on the horizontal axis (the years). The measureFn function specifies what goes on the vertical axis (the developer count). The colorFn function specifies the color of each bar.
The underscores in these functions mean you do not need that parameter. The library passes two parameters to each function, but you only use the first one.
The Container wraps your chart with specific dimensions. The Card gives it a material style appearance. The Text widget adds a title. The Expanded widget makes the chart fill available space.
The animate: true parameter makes the chart animate in when the screen loads. This is purely visual, but it adds polish.
DISPLAYING THE CHART
Now go back to your HomePage and import the new chart widget:
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:my_charts_app/developer_series.dart';
import 'package:my_charts_app/developer_chart.dart';
class HomePage extends StatelessWidget {
final List<DeveloperSeries> data = [
DeveloperSeries(
year: 2017,
developers: 40000,
barColor: charts.ColorUtil.fromDartColor(Colors.green),
),
DeveloperSeries(
year: 2018,
developers: 50000,
barColor: charts.ColorUtil.fromDartColor(Colors.green),
),
DeveloperSeries(
year: 2019,
developers: 60000,
barColor: charts.ColorUtil.fromDartColor(Colors.green),
),
DeveloperSeries(
year: 2020,
developers: 55000,
barColor: charts.ColorUtil.fromDartColor(Colors.green),
),
DeveloperSeries(
year: 2021,
developers: 75000,
barColor: charts.ColorUtil.fromDartColor(Colors.green),
),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: DeveloperChart(data: data),
),
);
}
}
Now run your app. You should see a green bar chart with five bars representing each year. The bars animate in smoothly. The chart has a title and sits in a card with padding.
This is your first working chart. Everything else is customization.
CREATING PIE CHARTS WITH THE SAME DATA
Pie charts show parts of a whole. They work best with category data. The good news is you can convert your bar chart to a pie chart with minimal changes.
Update your DeveloperChart widget to use PieChart instead of BarChart:
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:my_charts_app/developer_series.dart';
class DeveloperChart extends StatelessWidget {
final List<DeveloperSeries> data;
DeveloperChart({@required this.data});
@override
Widget build(BuildContext context) {
List<charts.Series<DeveloperSeries, String>> series = [
charts.Series(
id: "developers",
data: data,
domainFn: (DeveloperSeries series, _) => series.year.toString(),
measureFn: (DeveloperSeries series, _) => series.developers,
colorFn: (DeveloperSeries series, _) => series.barColor,
)
];
return Container(
height: 300,
padding: EdgeInsets.all(25),
child: Card(
child: Padding(
padding: const EdgeInsets.all(9.0),
child: Column(
children: <Widget>[
Text(
"Developer Distribution by Year",
style: Theme.of(context).textTheme.body2,
),
Expanded(
child: charts.PieChart(
series,
animate: true,
defaultRenderer: charts.ArcRendererConfig(
arcRendererDecorators: [
charts.ArcLabelDecorator(
labelPosition: charts.ArcLabelPosition.inside,
)
],
),
),
)
],
),
),
),
);
}
}
The key change is charts.PieChart instead of charts.BarChart. The defaultRenderer parameter configures how the pie chart looks. The ArcLabelDecorator adds labels inside each slice. These labels show the values or percentages.
Run your app. The same data now displays as a pie chart. Each slice represents one year’s developer count relative to the total.
BUILDING LINE CHARTS FOR TRENDS
Line charts show trends over time. They work differently from bar and pie charts. Line charts need numeric data on both axes, not just categories.
Create a new file called line_chart.dart:
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:my_charts_app/developer_series.dart';
class DeveloperLineChart extends StatelessWidget {
final List<DeveloperSeries> data;
DeveloperLineChart({@required this.data});
@override
Widget build(BuildContext context) {
List<charts.Series<DeveloperSeries, num>> series = [
charts.Series(
id: "developers",
data: data,
domainFn: (DeveloperSeries series, _) => series.year,
measureFn: (DeveloperSeries series, _) => series.developers,
colorFn: (DeveloperSeries series, _) => series.barColor,
)
];
return Container(
height: 300,
padding: EdgeInsets.all(25),
child: Card(
child: Padding(
padding: const EdgeInsets.all(9.0),
child: Column(
children: <Widget>[
Text(
"Developer Growth Trend",
style: Theme.of(context).textTheme.body2,
),
Expanded(
child: charts.LineChart(
series,
domainAxis: const charts.NumericAxisSpec(
tickProviderSpec: charts.BasicNumericTickProviderSpec(
zeroBound: false,
),
viewport: charts.NumericExtents(2016.0, 2022.0),
),
animate: true,
),
)
],
),
),
),
);
}
}
Notice the series type changed from List> to List>. Line charts use numeric data on both axes.
The domainAxis parameter controls the horizontal axis. The zeroBound: false parameter prevents the chart from starting at zero. The viewport parameter sets the range to display from 2016 to 2022.
This gives you a line chart that shows the trend in developer growth over time. The line connects each year to the next, making the trend obvious.
ADDING COLOR AND STYLING
Your charts work. Now make them visually distinct. Different data types benefit from different colors.
Update your developer_series.dart to include multiple colors:
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter/foundation.dart';
class DeveloperSeries {
final int year;
final int developers;
final charts.Color barColor;
DeveloperSeries({
@required this.year,
@required this.developers,
@required this.barColor
});
factory DeveloperSeries.fromJson(Map<String, dynamic> json) {
return DeveloperSeries(
year: json['year'],
developers: json['developers'],
barColor: charts.ColorUtil.fromDartColor(Colors.blue),
);
}
}
You could also create multiple series with different colors:
final List<DeveloperSeries> data = [
DeveloperSeries(
year: 2017,
developers: 40000,
barColor: charts.ColorUtil.fromDartColor(Colors.red),
),
DeveloperSeries(
year: 2018,
developers: 50000,
barColor: charts.ColorUtil.fromDartColor(Colors.blue),
),
DeveloperSeries(
year: 2019,
developers: 60000,
barColor: charts.ColorUtil.fromDartColor(Colors.green),
),
// ... more data points
];
This approach gives each bar its own color. For pie charts, this creates a more visually interesting design.
HANDLING REAL DATA FROM APIS
Your sample data works for learning. Production apps fetch data from servers. The process remains the same.
Imagine your API returns JSON like this:
[
{"year": 2017, "developers": 40000},
{"year": 2018, "developers": 50000},
{"year": 2019, "developers": 60000}
]
Create a service to fetch and parse this data:
import 'package:http/http.dart' as http;
import 'dart:convert';
class DeveloperService {
static Future<List<DeveloperSeries>> fetchDevelopers() async {
final response = await http.get('https://api.example.com/developers');
if (response.statusCode == 200) {
final json = jsonDecode(response.body);
final list = json as List;
return list
.map((item) => DeveloperSeries(
year: item['year'],
developers: item['developers'],
barColor: charts.ColorUtil.fromDartColor(Colors.green),
))
.toList();
} else {
throw Exception('Failed to load developers');
}
}
}
Then use this service in a StatefulWidget to load data when the screen opens:
import 'package:flutter/material.dart';
import 'package:my_charts_app/services/developer_service.dart';
import 'package:my_charts_app/developer_chart.dart';
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Future<List<DeveloperSeries>> futureData;
@override
void initState() {
super.initState();
futureData = DeveloperService.fetchDevelopers();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder<List<DeveloperSeries>>(
future: futureData,
builder: (context, snapshot) {
if (snapshot.hasData) {
return DeveloperChart(data: snapshot.data);
} else if (snapshot.hasError) {
return Text("Error: ${snapshot.error}");
}
return CircularProgressIndicator();
},
),
),
);
}
}
This code uses FutureBuilder to handle the async data fetch. While data loads, the app shows a spinner. When data arrives, the chart renders. If an error occurs, you show an error message.
CUSTOMIZING CHART APPEARANCE
Your charts can be more sophisticated. Add legends, change spacing, adjust colors, and more.
For a legend, use the behaviorSpec parameter:
charts.BarChart(
series,
animate: true,
behaviors: [
new charts.ChartTitle('Year',
behaviorPosition: charts.BehaviorPosition.start,
titleOutsideChart: true),
new charts.ChartTitle('Developer Count',
behaviorPosition: charts.BehaviorPosition.start,
titleOutsideChart: true),
new charts.LegendOptions(
position: charts.BehaviorPosition.bottom,
outsideJustification: charts.OutsideJustification.middleDrawArea)
],
)
This adds a legend and axis titles. The position parameters control where elements appear.
For margins and spacing, wrap your chart in a container with custom padding:
Container(
height: 400,
padding: EdgeInsets.all(30),
child: charts.BarChart(series, animate: true),
)
Increasing the padding creates breathing room around your chart.
COMMON MISTAKES TO AVOID
When building charts, beginners make predictable mistakes.
First, forgetting to import charts_flutter as charts. Your code will not compile without this. The import statement looks odd but it is necessary.
Second, using the wrong axis type. Line charts use num. Bar and pie charts use String. If you mix these up, your chart will not render correctly.
Third, not wrapping your chart in a Container with a height. Charts need bounded space. Without this, they throw layout errors.
Fourth, setting animate: true on every chart rebuild. If your data refreshes frequently, animations become annoying. Use animate: false for static charts or save animations for the initial load.
Fifth, ignoring touch interactions. charts_flutter supports tapping and dragging. Use tapListeners to add interactivity.
WHEN TO USE ALTERNATIVES
charts_flutter is strong, but it is not the only option.
Use fl_chart if you need highly customizable designs. fl_chart lets you control every pixel. The tradeoff is more code.
Use BezierChart if you specifically want smooth curved lines. charts_flutter line charts are straight. If your design requires curves, BezierChart is your answer.
Use MPFlutterChart if you need bubble charts, scatter plots, or candle charts. charts_flutter does not include these chart types.
For most projects, charts_flutter remains the best choice. It balances simplicity with functionality. Your code stays readable. Your charts look professional.
MOVING FORWARD
You now have the tools to add any chart type to your Flutter apps. You know how to structure data. You know how to build chart widgets. You know how to fetch real data from APIs.
The next step is experimentation. Build charts with your own data. Mix chart types. Combine charts in a dashboard. Animate transitions between chart views.
The patterns you have learned apply to every chart type. Create a series. Configure the axes. Customize the appearance. Render with animation.
charts_flutter is a complete charting solution for Flutter apps. You do not need anything else to build professional data visualizations.
Start building. Your users want to see their data in visual form. Give them that clarity. Give them those charts.