Flutter simple app
I have to build a simple app to learn flutter and publish it in the Store
the features of the app
1- List in the home
2- Navigation Drawer
3- Detail page with multi Cards that have large text content so need Scroll
4- Swipe card with change title bar and contain of the Cards
5- menu item show dialog can hide and show Card in Detail page
if you want go to code direct the link of the code
https://gitlab.com/proAhmed/flutter_ajury
The app has:
- model and repository for build data in list
- the main page app which run the app
- home screen contain navigation drawer and list
- details screen has swipe and show details data
main file is to run the app
void main() => runApp(HadithApp());
hadith app file to run the app with material design, title ,hide debug image
and launch the next screen
import 'package:flutter/material.dart';
import 'home.dart';
class HadithApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '40 Hadith',
debugShowCheckedModeBanner:false,
home: HomePage(),
);
}
}
HomePage
I load list of object Hadith from repository
List<Hadith> _list() {
List<Hadith> hadith = HadithRepository.loadHadiths();
return hadith;
}
I build list of card
List<Card> _buildGridCard(BuildContext context) {
List<Hadith> hadiths = _list();
if (hadiths == null || hadiths.isEmpty) {
return const <Card>[];
}
return hadiths.map((hadith) {
return Card(
clipBehavior: Clip.antiAlias,
elevation: 4.0,
color: Colors.white,
child: new InkWell(
onTap: () {
navigate(context, hadiths,hadith);
},
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Padding(
padding: EdgeInsets.fromLTRB(16.0, 30.0, 16.0, 4.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
hadith == null ? '' : hadith.name,
style: TextStyle(
color: Colors.black.withOpacity(0.8),
fontSize: 20,
),
softWrap: false,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
],
),
),
)
],
),
),
);
}).toList();
}
I Iterate over list and using each object to build card with specific data
Card has properties:
- elevation
- color
- child using InkWell to set :
- padding
- height
- onTap Action
- child of them to set text
Key to add open drawer function
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
_scaffoldKey to open drawer
onPressed: () {
_scaffoldKey.currentState.openDrawer();
}),
method to build drawer add list inside drawer iterate over list by map to build card with data
Drawer _buildDrawer(BuildContext context) {
return Drawer(
child: Column(
children: <Widget>[
SizedBox(height: 20.0),
MediaQuery.removePadding(
context: context,
removeTop: true,
child: Expanded(
child: ListView(
dragStartBehavior: DragStartBehavior.down,
padding:
const EdgeInsets.only(top: 4.0, left: 8.0, right: 8.0),
children: <Widget>[
Stack(
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: _list().map<Widget>((Hadith hadith) {
return Card(
clipBehavior: Clip.antiAlias,
elevation: 4.0,
color: Colors.white,
child: ListTile(
title: Text(
hadith.name,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16),
),
onTap: () {
navigate(
context,
_list(),
hadith,
);
},
),
);
}).toList(),
),
],
),
],
),
),
),
],
),
);
}
Main method to build the screen
@override
Widget build(BuildContext context) {
return Scaffold(
drawerDragStartBehavior: DragStartBehavior.down,
key: _scaffoldKey,
appBar: AppBar(
title: Text('الأربعون الأجرية'),
backgroundColor: Color.fromRGBO(20, 65, 76, 100),
brightness: Brightness.dark,
centerTitle: true,
leading: IconButton(
icon: Icon(
Icons.menu,
semanticLabel: 'menu',
),
onPressed: () {
_scaffoldKey.currentState.openDrawer();
}),
),
drawer: _buildDrawer(context),
body: GridView.count(
crossAxisCount: 1,
padding: EdgeInsets.all(16.0),
childAspectRatio: 8.0 / 2.0,
children: _buildGridCard(context),
),
);
}
and navigation to second screen
void navigate(BuildContext context, List<Hadith> hadithList, Hadith hadith) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
DetailPage(hadithList: hadithList, hadith: hadith)));
}
Detail Screen
I have 3 Cards and popup menu can show and hide of card and swipe that update the data of cards with list
I send the list when navigate to new Screen and current object clicked
DetailPages(List<Hadith> hadithLst, Hadith hadith) {
this.hadithLst = hadithLst;
this.hadith = hadith;
}
I use object to set title in appBar
void setTitleBar(Hadith hadith) {
setState(() {
appBarTitleText = Text(hadith.name,
style: TextStyle(color: Colors.white.withOpacity(0.8), fontSize: 20),
textAlign: TextAlign.center);
});
}
I use index to can navigate by get hadith Id to get the right object from list
int chosenIndex = 0;@override
void initState() {
// TODO: implement initState
super.initState();
hadithTitle = hadith.name;
setState(() {
chosenIndex = hadith.id;
hadithTitle = hadithLst[hadith.id].name;
});
}
Then I build my cards
Card _detailsCard(){
return Card(
elevation: 3.0,
color: Colors.white,
child: Column(
children: <Widget>[
SizedBox(height: 20.0),
new Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
),
margin: const EdgeInsets.only(left: 10.0, right: 10.0),
child: new Text(
hadithLst[chosenIndex].hadithDetails,
style: TextStyle(
color: Colors.black,
fontSize: 18,
),
textAlign: TextAlign.center,
),
),
SizedBox(height: 16.0),
],
),
);
}
I use popup menu to show and hide of cards by Boolean and set state to update UI
PopupMenuButton<String> _buildPopupMenu() {
return PopupMenuButton<String>(
onSelected: (String value) {
setState(() {
if (value == 'hadith') {
if (showHadith) {
showHadith = false;
} else {
showHadith = true;
}
} else if (value == "ajury") {
if (showExplain) {
showExplain = false;
} else {
showExplain = true;
}
} else {
if (showSource) {
showSource = false;
} else {
showSource = true;
}
}
});
},
itemBuilder: (BuildContext context) => <PopupMenuItem<String>>[
const PopupMenuItem<String>(
value: "hadith",
child: Text('الحديث'),
),
const PopupMenuItem<String>(
value: "ajury",
child: Text('شرح الإمام الأجرى'),
),
const PopupMenuItem<String>(
value: "source",
child: Text('تخريج الحديث'),
),
],
);
}
Then I use ListView to Vertical Scroll that build cards
ListView _showCards(){
return new ListView(
shrinkWrap: true,
padding: EdgeInsets.all(15.0),
children: <Widget>[
SizedBox(height: 20.0),
Visibility(
visible: showHadith,
child: _detailsCard(),
),
Visibility(
visible: showExplain,
child: SizedBox(height: 30.0),
),
Visibility(
visible: showExplain,
child: _explainCard(),
),
Visibility(
visible: showSource,
child: SizedBox(height: 30.0),
),
Visibility(
visible: showSource, //Default is true,
child: _sourceCard(),
),
],
);
}
Then Add this ListView inside Dismissible to enable swipe and update index by swipe by setState()
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: Text(hadithTitle,
style:
TextStyle(color: Colors.white.withOpacity(0.8), fontSize: 20),
textAlign: TextAlign.center),
backgroundColor: Color.fromRGBO(20, 65, 76, 100),
centerTitle: true,
actions: <Widget>[
_buildPopupMenu(),
],
),
body: new Dismissible(
resizeDuration: null,
onDismissed: (DismissDirection direction) {
setState(() {
chosenIndex += direction == DismissDirection.endToStart ? 1 : -1;
hadithTitle = hadithLst[chosenIndex].name;
});
},
key: new ValueKey(chosenIndex),
child: _showCards(),
),
);
}
I hope I have give clear explain and if you want see the app you can download
if you find the article good I hope you clap Thanks for your time