Flutter API Get using Bloc state management and http plugin
資料來源 => 我的 IT 幫幫忙 文章。
GitHub => WenYeh GitHub
資料夾結構
|-- lib
|-- bloc // bloc 資料夾
|-- restaurant_bloc.bloc.dart
|-- restaurant_bloc.event.dart
|-- restaurant_bloc.state.dart
|-- data // 用來裝 respository、model
|-- model
|-- api_result_model.dart
|-- respository
|-- restaurant_respository.dart
|-- res
|-- string
|-- strings.dart
|-- ui
|-- pages
|-- about_page.dart
|-- home_page.dart
|-- main.dart
JSON 格式
{
"success" : true,
"message" : "SUCCESS",
"data" : [ {
"id" : 0,
"name" : "八方雲集沙鹿靜宜店",
"description" : "營業時間:10:00 – 21:00n中午不外送",
"phone" : "0426321128",
"address" : "台中市沙鹿區英才路17號",
"lowest_price" : 300
}, {
"id" : 1,
"name" : "偉哥鹹酥雞台中靜宜店",
"description" : "營業時間:15:30 – 12:15n沒有提供外送服務",
"phone" : "0423809838",
"address" : "臺中市沙鹿區北勢東路517之1號",
"lowest_price" : 0
} ]
}
介紹套件
Api
我是使用 http 的套件來串接 API。
這邊是官網介紹 https://flutter.dev/docs/cookbook/networking/fetch-data
另外一種也是很多人用的 API 串接方式。(當然還有很多,在這邊不一一介紹)。
Dio
Bloc 相關套件
JSON 轉成物件的小技巧
我是使用 quicktype.io 來幫我自動轉成我要的物件。
Bloc 懶人包
Bloc 精髓
Event 事件
就是當你要取得資料====> 事件。
當你與畫面進行互動====> 事件。
State 狀態
取得資料中 => 狀態
取資料成功 => 狀態
取資料失敗 => 狀態
Bloc 處理事件屬於什麼狀態
去做判斷
如果這個事件 = 取得資料 那就屬於什麼狀態
進入主題開始寫程式
main.dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Api-Get',
home: BlocProvider(
create: (context) => RestaurantBloc(restaurantRepository: RestaurantRepository()),
child: HomePage(),
),
);
}
}
Bloc
restaurant_event.dart
abstract class RestaurantEvent extends Equatable{
@override
List Object> get props =>[];
}
class FetchRestaurantEvent extends RestaurantEvent {}
restaurant_state.dart
abstract class RestaurantState extends Equatable {
@override
ListObject> get props => [];
}
class RestaurantLoadingState extends RestaurantState {
}
class RestaurantSuccessState extends RestaurantState {
final RestaurantModel restaurantModel;
RestaurantSuccessState({@required this.restaurantModel});
@override
// TODO: implement props
ListObject> get props => [restaurantModel];
}
class RestaurantFailState extends RestaurantState {
final String message;
RestaurantFailState({@required this.message});
@override
// TODO: implement props
ListObject> get props => [message];
}
restaurant.bloc
class RestaurantBloc extends BlocRestaurantEvent, RestaurantState> {
RestaurantRepository restaurantRepository;
RestaurantBloc({@required this.restaurantRepository}) : super(RestaurantLoadingState());
RestaurantState get initialState => RestaurantLoadingState();
@override
StreamRestaurantState> mapEventToState(RestaurantEvent event) async* {
if (event is FetchRestaurantEvent) {
yield RestaurantLoadingState();
try {
RestaurantModel restaurantModel = await restaurantRepository.getRestaurantData();
print("Bloc Success");
yield RestaurantSuccessState(restaurantModel: restaurantModel);
} catch (e) {
print( await restaurantRepository.getRestaurantData());
yield RestaurantFailState(message: "???");
}
}
}
}
Repository
restaurant_result.dart
class RestaurantRepository {
FutureRestaurantModel> getRestaurantData() async {
final response = await http.get(AppStrings.cricArticleUrl);
if (response.statusCode == 200) {
Listint> bytes = response.body.toString().codeUnits;
var responseString = utf8.decode(bytes);
return RestaurantModel.fromJson(jsonDecode(responseString));
} else {
print("EEEE");
throw Exception();
}
}
}
model
api_result.model.dart
這部分請參考 quicktype 轉成物件。
UI 介面
home_page.dart
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends StateHomePage> {
RestaurantBloc _restaurantBloc;
Size size;
@override
void initState() {
super.initState();
_restaurantBloc = BlocProvider.ofRestaurantBloc>(context);
_restaurantBloc.add(FetchRestaurantEvent());
}
@override
Widget build(BuildContext context) {
size = MediaQuery.of(context).size;
return MaterialApp(
home: Builder(
builder: (context) {
return Material(
child: Scaffold(
appBar: AppBar(
title: Text("Api-Get"),
centerTitle: true,
actions: Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
_restaurantBloc.add(FetchRestaurantEvent());
},
),
IconButton(
icon: Icon(Icons.info),
onPressed: () {
navigateToAboutPage(context);
},
)
],
),
body: Container(
child: BlocListenerRestaurantBloc, RestaurantState>(
listener: (context, state) {
if (state is RestaurantFailState) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
),
);
}
},
child: BlocBuilderRestaurantBloc, RestaurantState>(
builder: (context, state) {
if (state is RestaurantLoadingState) {
return _buildLoading();
} else if (state is RestaurantSuccessState) {
return buildArticleList(state.restaurantModel);
} else if (state is RestaurantFailState) {
return _buildErrorUi(state.message);
}
return Container();
},
),
),
),
),
);
},
),
);
}
Widget _buildLoading() {
return Center(
child: CircularProgressIndicator(),
);
}
Widget _buildErrorUi(String message) {
return Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
message,
style: TextStyle(color: Colors.red),
),
),
);
}
Widget buildArticleList(RestaurantModel restaurantModel) {
return Container(
child: new ListView.builder(
padding: EdgeInsets.only(top: 30),
itemCount: restaurantModel.data.length,
itemBuilder: (context, int index) =>
buildCustomItem(context, index, restaurantModel.data),
));
}
void navigateToAboutPage(BuildContext context) {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return AboutPage();
}));
}
/// 餐廳 listView 的 Item 畫面
Widget buildCustomItem(BuildContext context, int index, ListDatum> data) {
return Container(
padding:
EdgeInsets.only(left: size.width * 0.1, right: size.width * 0.1),
// height: 500,
width: size.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Container(
// height: size.height * 0.3,
// decoration: BoxDecoration(
// borderRadius: BorderRadius.all(Radius.circular(25.0))),
// child: Image.network(RestaurantList[index].storeLink),
// // child: _ImgaeNetWorkStyle(RestaurantList[index].storeLink)
// ),
Padding(
padding: EdgeInsets.only(top: 16, right: 8.0, left: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
data[index].name,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.cyan[700]),
),
Text(
"最低$" + data[index].lowestPrice.toString() + "外送",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.cyan[700]),
),
],
),
),
Padding(
padding: EdgeInsets.all(8),
child: Text(data[index].description,
style: TextStyle(fontSize: 15, color: Colors.cyan[700])),
),
Padding(
padding: EdgeInsets.only(bottom: 16, right: 8.0, left: 8.0),
child: Text(data[index].address,
style: TextStyle(fontSize: 15, color: Colors.cyan[700])),
),
],
));
}
}
about_page.dart
class AboutPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("About"),
),
body: Container(
alignment: Alignment.center,
child: Text("Developer : wayne900204@gmail.com"),
),
);
}
}
結論
這是以上我寫 Flutter 並使用 Bloc 來串接 API 的實際例子。
這是我的 Github 開源碼 Flutter-Api-Get-Bloc 可以參考這裡。