openplanning

Bài thực hành Flutter với gói http và dart:convert (1)

  1. Cài đặt thư viện
  2. Model
  3. Rest Utils
  4. CarImagesSection
  5. CarDetailScreen
  6. MainApp
  7. Bài tập thực hành khác
Trong bài thực hành Flutter này chúng ta sẽ sử dụng gói thư viện http để lấy dữ liệu từ máy chủ dưới dạng văn bản JSON, sau đó sử dụng gói thư viện dart:convert chuyển đổi nó thành một đối tượng Dart và hiển thị trên Flutter.
{
  "brand": "Vinfast 3",
  "description": "Mini electric car with reasonable rental price.",
  "insurance": true,
  "location": "New York",
  "capacity" : 4,
  "engine": 43,
  "maxSpeed": 100,
  "rentalPrice": 20,
  "currency": "USD",
  "images": [
    "https://raw.githubusercontent.com/o7planning/rs/master/flutter/vinfast3-1.webp",
    "https://raw.githubusercontent.com/o7planning/rs/master/flutter/vinfast3-2.webp",
    "https://raw.githubusercontent.com/o7planning/rs/master/flutter/vinfast3-3.webp"
  ]
}

1. Cài đặt thư viện

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  http:

2. Model

_model.dart
import 'dart:convert';

class Car {
  String brand;
  String description;
  bool insurance;
  String location;
  int capacity;
  double engine;
  double maxSpeed;
  double rentalPrice;
  String currency;
  List<String> images;

  Car({
    required this.brand,
    required this.description,
    required this.insurance,
    required this.location,
    required this.capacity,
    required this.engine,
    required this.maxSpeed,
    required this.rentalPrice,
    required this.currency,
    required this.images,
  });

  Car.empty()
      : this(
          brand: '',
          description: '',
          insurance: true,
          location: '',
          capacity: 4,
          engine: 0,
          maxSpeed: 0,
          rentalPrice: 0,
          currency: 'USD',
          images: [],
        );

  static Car fromMap(Map<String, dynamic> map) {
    List<dynamic> imgs = map['images'];
    List<String> images = imgs.map((e) => e as String).toList();
    //
    return Car(
      brand: map['brand'],
      description: map['description'],
      insurance: map['insurance'],
      location: map['location'],
      capacity: map['capacity'],
      engine: map['engine'],
      maxSpeed: map['maxSpeed'],
      rentalPrice: map['rentalPrice'],
      currency: map['currency'],
      images: images,
    );
  }

  static Car fromJsonString(String jsonString) {
    Map<String, dynamic> map = jsonDecode(jsonString);
    return fromMap(map);
  }
}

3. Rest Utils

_rest_utils.dart
import 'dart:convert';

import 'package:http/http.dart' as http;

/// A Converter to convert responseText to Dart Object.
typedef Converter<D> = D Function(String responseText);

/// responseText = '{"errorMsg": "No Internet"}';
/// Return ==> "No Internet"
typedef ErrorConverter = String Function(String responseText);

/// responseText = '{"errorMsg": "No Internet"}';
/// Return ==> "No Internet"
String exampleErrorConverter(String responseText) {
  Map<String, dynamic> map = jsonDecode(responseText);
  return map['errorMsg'] ?? responseText;
}

class Result<D> {
  D? data;
  String? errorMessage;
  Result(this.data, this.errorMessage);
}

Future<Result<D>> fetchData<D>({
  required String urlString,
  Map<String, String>? headers,
  required Converter<D> converter,
  ErrorConverter? errorConverter,
}) async {
  Uri url = Uri.parse(urlString); // Uri object.

  D? data; // Dart Object.
  String? errorMessage;
  try {
    final response = await http.get(url, headers: headers);
    String responseText = response.body;

    if (response.statusCode == 200) {
      data = converter(responseText);
    } else {
      if (errorConverter != null) {
        errorMessage = errorConverter!(responseText);
      } else {
        errorMessage = responseText;
      }
    }
  } catch (e) {
    errorMessage = e.toString();
  }
  return Result(data, errorMessage);
}
Sử dụng hàm tiện ích ở trên để đọc một tài liệu JSON cho bởi một URL và chuyển đổi nó thành một đối tượng Dart.
Future<void> _loadCarDetail() async {
  var url =
      "https://raw.githubusercontent.com/o7planning/rs/master/rest/car/vinfast3.json";
  //
  Result<Car> result = await fetchData(
    urlString: url,
    converter: Car.fromJsonString,
  );
  if (result.errorMessage != null) {
    errorMessage = result.errorMessage;
  } else {
    car = result.data!;
  }
  setState(() {});
}

4. CarImagesSection

car_images_section.dart
import 'package:flutter/material.dart';

class CarImagesSection extends StatefulWidget {
  final List<String> imageUrls;

  const CarImagesSection(this.imageUrls, {super.key});

  @override
  State<StatefulWidget> createState() {
    return _CarImagesSectionState();
  }
}

class _CarImagesSectionState extends State<CarImagesSection> {
  int selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        SizedBox(
          height: 200,
          width: double.infinity,
          child: widget.imageUrls.isEmpty
              ? const SizedBox()
              : Image.network(widget.imageUrls[selectedIndex]),
        ),
        const SizedBox(height: 5),
        _buildImgIndicator(),
      ],
    );
  }

  Widget _buildImgIndicator() {
    List<Widget> children = [];
    for (int i = 0; i < widget.imageUrls.length; i++) {
      children.add(
        TextButton(
          onPressed: () {
            selectedIndex = i;
            setState(() {});
          },
          child: Text('${i + 1}'),
        ),
      );
    }
    return Row(
      mainAxisAlignment : MainAxisAlignment.center,
      children: children,
    );
  }
}

5. CarDetailScreen

Kết hợp tất cả các phần (Section) lại với nhau để tạo ra CarDetailScreen.
car_detail_screen.dart
import 'package:flutter/material.dart';

import '_model.dart';
import '_rest_utils.dart';
import 'box_info.dart';
import 'car_images_section.dart';

class CarDetailScreen extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _CarDetailScreenState();
  }
}

class _CarDetailScreenState extends State<CarDetailScreen> {
  Car car = Car.empty();
  String? errorMessage;

  @override
  void initState() {
    super.initState();
    _loadCarDetail();
  }

  Future<void> _loadCarDetail() async {
    var url =
        "https://raw.githubusercontent.com/o7planning/rs/master/rest/car/vinfast3.json";
    //
    Result<Car> result = await fetchData(
      urlString: url,
      converter: Car.fromJsonString,
    );
    if (result.errorMessage != null) {
      errorMessage = result.errorMessage;
    } else {
      car = result.data!;
    }
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        child: Padding(
          padding: const EdgeInsets.all(10),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              const Center(
                child: Text(
                  'Car Details',
                  style: TextStyle(fontSize: 30),
                ),
              ),
              if (errorMessage != null) const SizedBox(height: 10),
              if (errorMessage != null)
                Text(
                  errorMessage!,
                  style: const TextStyle(color: Colors.red),
                ),
              const SizedBox(height: 10),
              CarImagesSection(car.images),
              const SizedBox(height: 10),
              _buildInfo1(),
              const Divider(),
              _buildInfo2(),
              const SizedBox(height: 10),
              _buildInfo3(),
              const SizedBox(height: 20),
              _buildPriceInfo(),
              const SizedBox(height: 10),
              Center(
                child: ElevatedButton(
                  onPressed: () {},
                  child: const Text("Book Now"),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildInfo1() {
    return Column(
      children: [
        Text(car.brand, style: const TextStyle(fontSize: 30)),
        const SizedBox(height: 10),
        Text(car.description),
      ],
    );
  }

  Widget _buildInfo2() {
    Color color = Colors.indigo;

    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(Icons.location_on, color: color),
        Text(car.location, style: TextStyle(color: color)),
        const SizedBox(width: 10),
        Icon(Icons.person, color: color),
        Text('With a driver', style: TextStyle(color: color)),
        const SizedBox(width: 10),
        Checkbox(
          checkColor: color,
          value: car.insurance,
          onChanged: null,
        ),
        Text('Insurance', style: TextStyle(color: color)),
      ],
    );
  }

  Widget _buildInfo3() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        BoxInfo(
          icon: const Icon(Icons.directions_bus),
          text1: 'Capacity',
          text2: '${car.capacity} seats',
        ),
        const SizedBox(width: 20),
        BoxInfo(
          icon: const Icon(Icons.energy_savings_leaf),
          text1: 'Engine',
          text2: '${car.engine} HP',
        ),
        const SizedBox(width: 20),
        BoxInfo(
          icon: const Icon(Icons.directions_bus),
          text1: 'Max speed',
          text2: '${car.maxSpeed} Km/h',
        ),
      ],
    );
  }

  Widget _buildPriceInfo() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        const Text("Rental price"),
        const SizedBox(width: 20),
        Text('${car.rentalPrice} ${car.currency}/Day'),
      ],
    );
  }
}
box_info.dart
import 'package:flutter/material.dart';

class BoxInfo extends StatelessWidget {
  final Icon icon;
  final String text1;
  final String text2;

  const BoxInfo({
    super.key,
    required this.icon,
    required this.text1,
    required this.text2,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      constraints: const BoxConstraints(minWidth: 100),
      decoration: BoxDecoration(
        color: Colors.indigo.withAlpha(30),
      ),
      child: Padding(
        padding: const EdgeInsets.all(10),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            icon,
            const SizedBox(height: 5),
            Text(text1),
            const SizedBox(height: 5),
            Text(text2, style: const TextStyle(fontWeight: FontWeight.bold)),
          ],
        ),
      ),
    );
  }
}

6. MainApp

main.dart
import 'package:flutter/material.dart';

import 'car_detail_screen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: CarDetailScreen(),
    );
  }
}

Các hướng dẫn lập trình Flutter

Show More