Flutter Starter: Guide to building a Hacker News App

Search for a command to run...

No comments yet. Be the first to comment.
Is the local IDE dead? Sanket Sahu discusses the rise of 'vibe-coding' and how browser-native tools like RapidNative are reshaping the future of mobile app development.

OpenClaw is a powerful, self-hosted AI assistant that connects to your tools to perform actions. Explore its Gateway architecture, real-world use cases, and security precautions.

Discover how neo-brutalism is shaping 2026 design trends. See how anti-design principles can create distinct, usable, and memorable product experiences.

When code breaks a pipeline, developers have to stop working and figure out why. This blog shows how an AI agent reads the error, finds the fix, and submits it for review all on its own.

GeekyAnts built a 5-agent fraud detection pipeline that makes decisions in under 200ms — 15x cheaper than single-model systems, with full explainability built in.

GeekyAnts Tech Blog
349 posts
GeekyAnts is an AI-powered digital product engineering and consulting company helping startups, enterprises, and Fortune 500 brands build scalable, future-ready digital solutions. Since 2006, we have delivered 800+ successful projects for 550+ global clients across healthcare, BFSI, retail, logistics, education, and enterprise technology. We help businesses accelerate digital transformation through strategy, design, engineering, and AI-led innovation.
Are you confused about how to get started with Flutter? Use Flutter Starter to create a functional Hacker News App in no time! Flutter Starter is a kit that enables coders to build production-ready apps. It has API configurations, state management, UI style guides and configurations for Flutter projects.
Firstly, use the template from Github. Proceed to the examples folder, go to the hacker-news folder and run it. Now, let us start with the API SDK. Using the rest method, insert the API links in api_constants.dart:
Map<String, String> apiConstants = {
"hacker_news": "https://hacker-news.firebaseio.com/v0",
"auth": "https://reqres.in/api"
};
In main.dart, we need 2 functions fetchTopIds and fecthItems. fetchTopIds will get all the top IDs for news and fetchItems will take the ID and give us the content for that news ID.
Both of them are get methods so we will call get from restapihandler as shown below:
static fetchTopId() async {
final response = await RestApiHandlerData.getData(
'${apiConstants["hacker_news"]}/topstories.json');
return response;
}
static fetchItems(int id) async {
final response = await RestApiHandlerData.getData(
'${apiConstants["hacker_news"]}/item/$id.json');
return response;
}
The API SDK is now a black box from which we will just call the methods fetchTopId and fetchItems, which will return a JSON response.
Now that we have the data, it needs to be called, parsed and stored. Proceed to the shared folder which contains blocs, models and resources.
Resources Use a provider as shown below:
class NewsApiProvider implements Source {
Future<List<int>> fetchTopId() async {
final response = await ApiSdk.fetchTopId();
final ids = response;
return ids.cast<int>();
}
Future<NewsItemModel> fetchItems(int id) async {
final response = await ApiSdk.fetchItems(id);
final parsedJson = response;
return NewsItemModel.fromJson(parsedJson);
}
}
Then, create a repository in which we will call the API SDK functions to get the data. This will be parsed in the models folder.
The news_item_model.dart file in the models folder contains the following code:
import 'dart:convert';
class NewsItemModel {
final int id;
final bool deleted;
final String type;
final String by;
final int time;
final String text;
final bool dead;
final int parent;
final List<dynamic> kids;
final String url;
final int score;
final String title;
final int descendants;
NewsItemModel.fromJson(Map<String, dynamic> parsedJson)
: id = parsedJson['id'],
deleted = parsedJson['deleted'] ?? false,
type = parsedJson['type'],
by = parsedJson['by'] ?? '',
time = parsedJson['time'],
text = parsedJson['text'] ?? '',
dead = parsedJson['dead'] ?? false,
parent = parsedJson['parent'],
kids = parsedJson['kids'] ?? [],
url = parsedJson['url'],
score = parsedJson['score'],
title = parsedJson['title'],
descendants = parsedJson['descendants'] ?? 0;
NewsItemModel.fromDb(Map<String, dynamic> parsedJson)
: id = parsedJson['id'],
deleted = parsedJson['deleted'] == 1,
type = parsedJson['type'],
by = parsedJson['by'],
time = parsedJson['time'],
text = parsedJson['text'],
dead = parsedJson['dead'] == 1,
parent = parsedJson['parent'],
kids = jsonDecode(parsedJson['kids']),
url = parsedJson['url'],
score = parsedJson['score'],
title = parsedJson['title'],
descendants = parsedJson['descendants'];
Map<String, dynamic> toMap() {
return <String, dynamic>{
'id': id,
'type': type,
'by': by,
'time': time,
'text': text,
'parent': parent,
'url': url,
'score': score,
'title': title,
'descendants': descendants,
'deleted': deleted ? 1 : 0,
'dead': dead ? 1 : 0,
'kids': jsonEncode(kids),
};
}
}
To parse the JSON data, go to the repository folder and open news-repository.dart. We will call the function to parse it like this:
Future<List<int>> fetchTopId() {
return sources[1].fetchTopId();
}
Future<NewsItemModel> fetchItem(int id) async {
NewsItemModel item;
var source;
for (source in sources) {
item = await source.fetchItems(id);
if (item != null) {
break;
}
}
for (var cache in caches) {
if (cache != source) {
cache.addItem(item);
}
}
return item;
}
The data has been parsed using models and resources. Lastly, we will create blocs.
For this example, we used rxdart. In stories_bloc.dart, we have used the repository to access data as shown below:
_itemTransformer() {
return ScanStreamTransformer(
(Map<int, Future<NewsItemModel>> cache, int id, index) {
cache[id] = _repository.fetchItem(id);
return cache;
},
<int, Future<NewsItemModel>>{},
);
}
With help of providers and rxdart, we will send the data to the main app. In the main app, List widget needs to be wrapped with the provider widget:
body: StoriesProvider(child: NewsList()),
Now in NewsList widget, make the following changes:
class NewsList extends StatelessWidget {
Widget build(BuildContext context) {
final storiesBloc = StoriesProvider.of(context);
storiesBloc.fetchTopIds();
return topIdList(storiesBloc);
}
}
The stories bloc is accessed by first we accessing the instance and then calling the fetch function.
Finally, use the l StreamBuilder widget to build the UI:
import 'package:app/src/widgets/news_list_tiles.dart';
import 'package:app/src/widgets/refresh.dart';
import 'package:flutter/material.dart';
import 'package:shared/main.dart';
class NewsList extends StatelessWidget {
Widget build(BuildContext context) {
final storiesBloc = StoriesProvider.of(context);
storiesBloc.fetchTopIds();
return topIdList(storiesBloc);
}
Widget topIdList(StoriesBloc storiesBloc) {
return StreamBuilder(
stream: storiesBloc.topId,
builder: (BuildContext context, AsyncSnapshot<List<int>> snapShot) {
if (!snapShot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return Refresh(
child: ListView.builder(
itemCount: snapShot.data.length,
itemBuilder: (BuildContext context, int index) {
storiesBloc.fetchItem(snapShot.data[index]);
return NewsListTile(itemId: snapShot.data[index]);
},
),
);
},
);
}
}
Your app should look like this:
Congratulations! You've now created your very own Hacker News App. If you would like to learn more about Flutter Starter, please visit our official documentation.
This article was written by Sumant Raj and edited by Kavya V.