iT邦幫忙

1

Flutter API Get using Bloc state management and http plugin

Flutter API Get using Bloc state management and http plugin

這是我第一次寫(不專業技術文,請大家多多包涵),希望大家會喜歡。可以留言問我問題,或是給我一點寫技術文件的意見。我有看到都會回復呦~


資料夾結構

|-- 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:00\n中午不外送",
    "phone" : "0426321128",
    "address" : "台中市沙鹿區英才路17號",
    "lowest_price" : 300
  }, {
    "id" : 1,
    "name" : "偉哥鹹酥雞台中靜宜店",
    "description" : "營業時間:15:30 – 12:15\n沒有提供外送服務",
    "phone" : "0423809838",
    "address" : "臺中市沙鹿區北勢東路517之1號",
    "lowest_price" : 0
  } ]
}

介紹套件

API 串接相關套件

我是使用 http 的套件來串接 API。
官網介紹
另外一種也是很多人用的 API 串接方式。(當然還有很多,在這邊不一一介紹)。
Dio

Bloc 相關套件

flutter_bloc
equatable


JSON 轉成物件的小技巧

我是使用 quicktype 來幫我自動轉成我要的物件。


Bloc 懶人包

Bloc 精髓

Bloc 分成 3 部分,Event Bloc State
https://ithelp.ithome.com.tw/upload/images/20210224/20134548lbQEDixUjg.png

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
  List<Object> get props => [];
}

class RestaurantLoadingState extends RestaurantState {

}

class RestaurantSuccessState extends RestaurantState {

  final RestaurantModel restaurantModel;

  RestaurantSuccessState({@required this.restaurantModel});

  @override
  // TODO: implement props
  List<Object> get props => [restaurantModel];
}

class RestaurantFailState extends RestaurantState {

  final String message;

  RestaurantFailState({@required this.message});

  @override
  // TODO: implement props
  List<Object> get props => [message];
}

restaurant.bloc

class RestaurantBloc extends Bloc<RestaurantEvent, RestaurantState> {

  RestaurantRepository restaurantRepository;

  RestaurantBloc({@required this.restaurantRepository}) : super(RestaurantLoadingState());
  
  RestaurantState get initialState => RestaurantLoadingState();

  @override
  Stream<RestaurantState> 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  {

  Future<RestaurantModel> getRestaurantData() async {
    final response =  await http.get(AppStrings.cricArticleUrl);

    if (response.statusCode == 200) {
      List<int> 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 轉成物件。
https://ithelp.ithome.com.tw/upload/images/20210224/201345480Scp1BA2AH.png

UI 介面

home_page.dart

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  RestaurantBloc _restaurantBloc;
  Size size;
  @override
  void initState() {
    super.initState();
    _restaurantBloc = BlocProvider.of<RestaurantBloc>(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: BlocListener<RestaurantBloc, RestaurantState>(
                  listener: (context, state) {
                    if (state is RestaurantFailState) {
                      Scaffold.of(context).showSnackBar(
                        SnackBar(
                          content: Text(state.message),
                        ),
                      );
                    }
                  },
                  child: BlocBuilder<RestaurantBloc, 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, List<Datum> 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 可以參考這裡。


1 則留言

1
isaac0903
iT邦新手 5 級 ‧ 2021-02-24 20:52:29

甚麼啦 看不懂

邱文也 iT邦新手 5 級 ‧ 2021-02-25 09:38:17 檢舉

這個不是適合給新手看的辣,之後等你進入自我研究階段,你再來看就會懂了..

我要留言

立即登入留言