Skip to content

Instantly share code, notes, and snippets.

@dpossas
Last active September 11, 2020 20:39
Show Gist options
  • Save dpossas/690fa3d77303a1e87c0fa22306f591a0 to your computer and use it in GitHub Desktop.
Save dpossas/690fa3d77303a1e87c0fa22306f591a0 to your computer and use it in GitHub Desktop.
Workshop Flutter

Flutter - 3 horas de HandsOn

Informações sobre o Workshop

  1. Construiremos um aplicativo para gerenciamento de coleções HotWheels e abordaremos:

  2. Todo participante deve ter o ambiente flutter configurado com a versão estável (1.20.2 em 19/08/2020) e com o projeto hello world rodando em um emulador Para configurar o ambiente, siga as instruções em: https://flutter.dev/docs/get-started/install

  3. Usaremos o VSCode para desenvolver nosso projeto. Para isso, é necessário instalar o plugin de Flutter nesta IDE Poderão ser usadas outras IDEs, mas talvez eu não consiga lhe ajudar com os atalhos e particularidades :(

É necessário anotar a classe com a anotação

@JsonSerializable()

É necessário usar uma partial como:

 part 'my_class.g.dar';

Método para gerar objetos através de um json

 factory MyClass.fromJson(Map<String, dynamic> json) => _$MyClassFromJson(json);

Método para gerar um json através de um objeto

 Map<String, dynamic> toJson() => _$MyClassToJson(this);

Ao adicionar as linhas anteriores, a IDE acusará um erro. Ignore-o por enquanto

Para rodar o build-runner e gerar as classes necessárias para converter json-object / object-json

 flutter pub run build_runner build

No fim sua classe (my_class.dart) deve ser algo como

import 'package:json_annotation/json_annotation.dart';

part 'my_class.g.dart';

@JsonSerializable()
class MyClass {
  final int id;
  
  MyClass({this.id});
  
  factory MyClass.fromJson(Map<String, dynamic> json) => _$MyClassFromJson(json);
  Map<String, dynamic> toJson() => _$MyClassToJson(this);
}
import 'package:json_annotation/json_annotation.dart';
part "car.g.dart";
@JsonSerializable()
class Car {
@JsonKey(name: 'CarMetaID')
int id;
@JsonKey(
name: 'CarOrderNumber', fromJson: int.tryParse, toJson: objectToString)
int order;
@JsonKey(name: 'TotalItems')
int totalItems;
@JsonKey(name: 'Title')
String name;
@JsonKey(name: 'MiniCollectionId')
int collectionId;
@JsonKey(name: 'ColorCode')
String hexColor;
@JsonKey(name: 'Year')
int year;
@JsonKey(name: 'Packagingcode')
String packageCode;
@JsonKey(name: 'MainImages')
List<dynamic> mainImages;
Car({
this.id,
this.order,
this.name,
this.totalItems,
this.hexColor,
this.year,
this.packageCode,
this.mainImages,
});
factory Car.fromJson(Map<String, dynamic> json) => _$CarFromJson(json);
Map<String, dynamic> toJson() => _$CarToJson(this);
static String objectToString(dynamic v) {
return String.fromCharCode(v);
}
}
{
"CarMetaID": 102186,
"CarOrderNumber": "4",
"TotalItems": 10,
"Title": "'20 JEEP GLADIATOR",
"ToyNumber": "GHB41",
"CarMetaSEOName": "20-jeep-gladiator",
"CategoryId": null,
"MiniCollectionId": 1247,
"MiniCollection": "BAJA BLAZERS",
"GameCategoryId": 0,
"GameCategory": null,
"MiniCollectionUrl": "https://static-nv.mattel.com/HWCarCatalog/en-us/Images/baja-blazers-collection_tcm985-153435.png",
"MakeUrl": null,
"ColorUrl": "https://static.mattel.com/HWCarCatalog/en-us/Images/RED_tcm838-287586.png",
"SeriesUrl": null,
"ColorSEOName": "red",
"ColorCode": "#b2020b",
"MakeSEOName": "jeep",
"MiniCollectionSEOName": "baja-blazers",
"Year": 2020,
"SeriesId": null,
"Series": null,
"MakeId": 1156,
"Make": "Jeep",
"ColorId": 111,
"Color": "Red",
"Packagingcode": "lztsc",
"CarMetaDescription": null,
"LiveDate": null,
"IsNew": 0,
"DisplayFlag": false,
"Createddate": "0001-01-01T00:00:00",
"CreatedBy": null,
"Modifieddate": null,
"ModifiedBy": null,
"Styles": [
{
"Id": 1053,
"Name": "Truck",
"ImageUrl": "https://static-nv.mattel.com/HWCarCatalog/en-us/Images/Style_Truck_330px_tcm985-127487.png",
"Number": 1,
"StyleSEOName": "truck"
}
],
"ThreeQuarterUnity": [],
"Sideviewphotos": [
{
"URL": "https://media.mattel.com/root/HWCarsCatalog/App/SideView/300x164/GHB41_C_003.png",
"Resolution": "300X164",
"ContentId": null
},
{
"URL": "https://media.mattel.com/root/HWCarsCatalog/App/SideView/534x300/GHB41_C_003.png",
"Resolution": "534X300",
"ContentId": null
},
{
"URL": "https://media.mattel.com/root/HWCarsCatalog/App/SideView/710x400/GHB41_C_003.png",
"Resolution": "710X400",
"ContentId": null
}
],
"SideviewGIFphotos": [],
"Topdownphotos": [],
"UnityImages_iOS": [],
"UnityImages_Android": [],
"MainImages": [
{
"URL": "https://media.mattel.com/root/HWCarsCatalog/Web/MainImage/GHB41_C_003.png",
"Resolution": null,
"ContentId": null
}
],
"BrandImages": [],
"ThumbnailImages": [
{
"URL": "https://media.mattel.com/root/HWCarsCatalog/Web/Thumbnail/GHB41_C_003.png",
"Resolution": null,
"ContentId": null
}
],
"carouselImages": [],
"ThreeSixtyDegreeVideo": [],
"AnimationImage": []
}
import 'package:dio/dio.dart';
import 'package:workshop_enterprise/models/car.dart';
import 'package:workshop_enterprise/services/dio_provider_base.dart';
class CarRepository {
static const String URL_GET_CARS = "GetCars";
Dio dio = DioProviderBase().dio;
Future<List<Car>> get(int collectionId) async {
final _cars = List<Car>();
Response response = await dio.post(URL_GET_CARS, data: {});
if (response.statusCode == 200) {
List.castFrom(response.data).forEach(
(element) {
final car = Car.fromJson(element);
if (car.collectionId == collectionId) {
_cars.add(car);
}
},
);
return _cars;
}
return null;
}
}
{
"Id": 1117,
"Code": "985-120924-1024",
"Name": "HW CITY WORKS",
"BackGroundColor": "#fcde07",
"ForeGroundColor": "#000000",
"SEOName": "hw-city-works",
"TotalItems": 10,
"ImageUrl": "https://static-nv.mattel.com/HWCarCatalog/en-us/Images/HW_Cityworks_330px_tcm985-130360.png"
}
import 'package:flutter/material.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:workshop_codecon/models/car.dart';
import 'package:workshop_codecon/models/hw_collection.dart';
import 'package:workshop_codecon/repositories/car_repository.dart';
class CollectionDetails extends StatefulWidget {
final MiniCollection miniCollection;
const CollectionDetails({
Key key,
this.miniCollection,
}) : super(key: key);
@override
_CollectionDetailsState createState() => _CollectionDetailsState();
}
class _CollectionDetailsState extends State<CollectionDetails> {
List<int> inCollection = <int>[];
final carRepo = CarRepository();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.miniCollection.name),
centerTitle: true,
),
body: SafeArea(
child: Container(
child: FutureBuilder(
future: carRepo.get(widget.miniCollection.id),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
continue loading;
break;
loading:
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
break;
case ConnectionState.active:
// TODO: Handle this case.
break;
case ConnectionState.done:
List<Car> cars = snapshot.data;
return PageView(
controller: PageController(initialPage: 1),
scrollDirection: Axis.horizontal,
children: cars.map((Car car) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (car.mainImages.length > 0)
Image.network(car.mainImages[0]['URL']),
Text(car.name),
BookmarkIcon(
inCollection: inCollection,
carId: car.id,
)
],
),
);
}).toList(),
);
break;
}
},
),
),
),
);
}
}
class BookmarkIcon extends StatefulWidget {
List<int> inCollection;
int carId;
BookmarkIcon({Key key, this.inCollection, this.carId}) : super(key: key);
@override
_BookmarkIconState createState() => _BookmarkIconState();
}
class _BookmarkIconState extends State<BookmarkIcon> {
final carRepo = CarRepository();
Database database;
void loadDB() async {
var databasesPath = await getDatabasesPath();
String path = join(databasesPath, 'workshop_codecon.db');
database = await openDatabase(path, version: 2,
onCreate: (Database db, int version) async {
await db.execute('CREATE TABLE carros (id INTEGER)');
});
carregarMeusCarros();
}
void adicionarCarro(int id) async {
await database.insert("carros", {'id': id});
}
void removeCarro(int id) async {
await database.delete("carros", where: 'id = ?', whereArgs: [id]);
}
void carregarMeusCarros() async {
List<Map<String, dynamic>> r =
await database.rawQuery("select * from carros");
r.forEach((item) {
widget.inCollection.add(item['id']);
});
setState(() {
widget.inCollection = widget.inCollection;
});
}
@override
void initState() {
loadDB();
super.initState();
}
@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(widget.inCollection.contains(widget.carId)
? Icons.bookmark
: Icons.bookmark_border),
onPressed: () {
if (widget.inCollection.contains(widget.carId)) {
widget.inCollection.remove(widget.carId);
removeCarro(widget.carId);
} else {
widget.inCollection.add(widget.carId);
adicionarCarro(widget.carId);
}
setState(() {
widget.inCollection = widget.inCollection;
});
},
);
}
}
import 'package:workshop_enterprise/models/hw_collection.dart';
class CollectionDetailArgs {
final MiniCollection miniCollection;
CollectionDetailArgs(this.miniCollection);
}
import 'dart:ui';
extension ColorFromHexString on String {
Color hexToColor() {
return Color(int.tryParse(this.replaceAll("#", "0xff")));
}
}
import 'package:dio/dio.dart';
class DioProviderBase {
static const String URL_BASE = "https://product.mattel.com/api/ProductInfo/";
static final DioProviderBase _dioProviderBase = DioProviderBase._internal();
DioProviderBase._internal();
final dio = Dio(
BaseOptions(
baseUrl: URL_BASE,
connectTimeout: Duration(minutes: 3).inMilliseconds,
receiveTimeout: Duration(minutes: 5).inMilliseconds,
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
},
),
);
factory DioProviderBase() {
return _dioProviderBase;
}
Future<Response> get(String url, Map<String, dynamic> params) async {
return await dio.get(url, queryParameters: params ?? {});
}
Future<Response> post(String url, Map<String, dynamic> bodyData) async {
return await dio.post(url, data: bodyData ?? {});
}
}
import 'package:flutter/material.dart';
import 'package:workshop_enterprise/models/hw_collection.dart';
import 'package:workshop_enterprise/repositories/mini_collection_repository.dart';
import 'package:workshop_enterprise/screens/collection_details_args.dart';
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final collectionRepo = MiniCollectionRepository();
@override
Widget build(BuildContext context) {
return Material(
child: SafeArea(
child: Container(
child: FutureBuilder(
future: collectionRepo.get(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
continue loading;
break;
loading:
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
break;
case ConnectionState.active:
// TODO: Handle this case.
break;
case ConnectionState.done:
return ListView.separated(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
final MiniCollection miniCollection =
snapshot.data[index];
return ListTile(
leading: SizedBox(
width: 40,
height: 40,
child: Image.network(miniCollection.imageURL),
),
trailing: Icon(Icons.chevron_right),
title: Text(miniCollection.name),
onTap: () {
return Navigator.pushNamed(
context,
'/collectionDetails',
arguments: CollectionDetailArgs(miniCollection),
);
},
);
},
separatorBuilder: (context, index) => Divider(),
);
break;
}
return Text("texto");
},
),
),
),
);
}
}
import 'package:json_annotation/json_annotation.dart';
part "mini_collection.g.dart";
@JsonSerializable()
class MiniCollection {
@JsonKey(name: 'Id')
int id;
@JsonKey(name: 'Name')
String name;
@JsonKey(name: 'BackGroundColor')
String bgColor;
@JsonKey(name: 'ForeGroundColor')
String fgColor;
@JsonKey(name: 'SEOName')
String seoName;
@JsonKey(name: 'TotalItems')
int totalItems;
@JsonKey(name: 'ImageUrl')
String imageURL;
MiniCollection({
this.id,
this.name,
this.bgColor,
this.fgColor,
this.seoName,
this.totalItems,
this.imageURL,
});
factory MiniCollection.fromJson(Map<String, dynamic> json) =>
_$MiniCollectionFromJson(json);
Map<String, dynamic> toJson() => _$MiniCollectionToJson(this);
}
import 'package:dio/dio.dart';
import 'package:workshop_enterprise/models/hw_collection.dart';
import 'package:workshop_enterprise/services/dio_provider_base.dart';
class MiniCollectionRepository {
static const String URL_GET_COLLECTIONS = "GetAllMiniCollection";
Dio dio = DioProviderBase().dio;
Future<List<MiniCollection>> get() async {
final _miniCollections = List<MiniCollection>();
Response response = await dio.get(URL_GET_COLLECTIONS);
if (response.statusCode == 200) {
List.castFrom(response.data).forEach(
(element) => _miniCollections.add(MiniCollection.fromJson(element)),
);
return _miniCollections;
}
return null;
}
}
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.3
dio: ^3.0.10
json_annotation: ^3.0.1
sqflite: ^1.3.1
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^1.10.0
json_serializable: ^3.3.0
import 'package:flutter/material.dart';
import 'package:workshop_enterprise/screens/collection_details.dart';
import 'package:workshop_enterprise/screens/collection_details_args.dart';
import 'package:workshop_enterprise/screens/home_screen.dart';
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => HomeScreen(),
'/collectionDetails': (BuildContext context) {
final CollectionDetailArgs args = ModalRoute.of(context).settings.arguments;
return CollectionDetails(
miniCollection: args.miniCollection,
);
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment