Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
$ flutter runWhat are we building?
data class User(val name: String, val age: Int)import 'package:meta/meta.dart';
part 'freezed_classes.freezed.dart';
@immutable
abstract class User with _$User {
const factory User(String name, int age) = _User;
}@immutable
class User {
final String name;
final int age;
User(this.name, this.age);
User copyWith({
String name,
int age,
}) {
return User(
name ?? this.name,
age ?? this.age,
);
}
@override
bool operator ==(Object o) {
if (identical(this, o)) return true;
return o is User && o.name == name && o.age == age;
}
@override
int get hashCode => name.hashCode ^ age.hashCode;
}# pubspec.yaml
dependencies:
json_annotation: ^4.0.0
dev_dependencies:
build_runner:
json_serializable 4.0.2$ dart pub getimport 'package:json_annotation/json_annotation.dart';
part 'my_model.g.dart';import 'package:json_annotation/json_annotation.dart';
part 'my_model.g.dart';
@freezed
class Person with _$Person {
const factory Person({
@required String firstName,
@required String lastName,
}) = _Person;
factory Person.fromJson(Map<String, dynamic> json) =>
_$PersonFromJson(json);
}$ flutter pub run build_runner buildanalyzer:
exclude:
- "**/*.g.dart"


$ flutter pub getimport 'package:flutter/material.dart';
class ProductsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("Product Screen"),
),
);
}
}
void main() async {
runApp(Application());
}
import 'package:flutter/material.dart';
class Application extends StatelessWidget {
const Application({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Tech camp Flutter',
debugShowCheckedModeBanner: false,
home: Scaffold()
);
}
}
import 'package:auto_route/auto_route.dart';
import 'package:auto_route/auto_route_annotations.dart';
import 'package:flutter/cupertino.dart';
// Import your products_screen.dart
export 'router.gr.dart';
@MaterialAutoRouter(routes: [
MaterialRoute(page: ProductsScreen, initial: true),
])
class $Router {}
MaterialApp(
title: 'Tech camp Flutter',
debugShowCheckedModeBanner: false,
//TODO: Add this line
builder: ExtendedNavigator.builder(router: Router()),
),import 'package:flutter/material.dart' hide Router;# pubspec.yaml
dependencies:
dio: ^3.0.10import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:techamp_flutter_shopping_app/app.dart';
part 'product.freezed.dart';
part 'product.g.dart';
@freezed
abstract class Product with _$Product {
const factory Product({
@required int id,
@required String title,
@required String image,
@required double price,
@required String description,
@required @CategoryConverter() ProductCategory category,
}) = _Product;
factory Product.fromJson(Map<String, dynamic> json) =>
_$ProductFromJson(json);
}# pubspec.yaml
dependencies:
freezed_annotation:
dev_dependencies:
build_runner:
freezed:$ dart pub getimport 'package:freezed_annotation/freezed_annotation.dart';
part 'my_demo.freezed.dart';import 'package:freezed_annotation/freezed_annotation.dart';
part 'my_demo.freezed.dart';
@freezed
class Person with _$Person {
factory Person({ String name, int age }) = _Person;
}$ flutter pub run build_runner buildanalyzer:
exclude:
- "**/*.freezed.dart"class _ProductItem extends StatelessWidget {
final Product product;
const _ProductItem({Key key, @required this.product})
: assert(product != null),
super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => context.navigator.push(
Routes.productScreen,
arguments: ProductScreenArguments(product: product),
),
child: Card(
margin: EdgeInsets.zero,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: Container(
color: Colors.white,
padding: const EdgeInsets.all(8),
child: Image.network(product.image),
),
),
Container(
height: 48,
color: Colors.grey[200],
padding: const EdgeInsets.all(4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.title,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 4),
Text('${product.price} ETB'),
],
),
),
),
IconButton(
onPressed: () =>
context.read<CartCubit>().addProduct(product),
icon: const Icon(Icons.add_shopping_cart_rounded),
),
],
),
),
],
),
),
);
}
}part of 'products_cubit.dart';
@freezed
abstract class ProductsState with _$ProductsState {
const factory ProductsState.error({@required String error}) =
ProductsErrorState;
const factory ProductsState.refreshing() = ProductsRefreshingState;
const factory ProductsState.initial() = InitialProductsState;
const factory ProductsState.loaded({
@required List<Product> products,
}) = ProductsLoadedState;
const factory ProductsState.loading() = ProductsLoadingState;
}import 'package:dio/dio.dart';
import 'package:techamp_flutter_shopping_app/app.dart';abstract class ProductRepository {
Future<List<Product>> getAll();
}class ProductRepositoryImpl implements ProductRepository {
final Dio _dio;
const ProductRepositoryImpl(this._dio) : assert(_dio != null);
@override
Future<List<Product>> getAll() async {
try {
final response = await _dio.get('products');
final List productsJson = response.data;
return productsJson.map((json) => Product.fromJson(json)).toList();
} on DioError catch (_) {
throw const AppError('Network error');
} on dynamic catch (_) {
throw const AppError('Something went wrong.');
}
}
}import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:techamp_flutter_shopping_app/app.dart';
part 'products_cubit.freezed.dart';
part 'products_state.dart';
class ProductsCubit extends Cubit<ProductsState> {
final ProductRepository _productRepository;
ProductsCubit(this._productRepository)
: assert(_productRepository != null),
super(const InitialProductsState());
Future<void> getAll() {
emit(const ProductsLoadingState());
return _getAllProducts();
}
Future<void> refresh() async {
emit(const ProductsRefreshingState());
return _getAllProducts();
}
Future<void> _getAllProducts() async {
try {
final products = await _productRepository.getAll();
emit(ProductsLoadedState(products: products));
} on AppError catch (error) {
emit(ProductsErrorState(error: error.message));
}
}
}class _ProductsGridView extends StatelessWidget {
final List<Product> products;
const _ProductsGridView({Key key, @required this.products})
: assert(products != null),
super(key: key);
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 1 / 1.2,
),
padding: const EdgeInsets.all(8),
itemCount: products.length,
physics: const BouncingScrollPhysics(),
itemBuilder: (_, index) => _ProductItem(product: products[index]),
);
}
}import 'package:dio/dio.dart';var dio = Dio();
Response response = await dio.get('https://google.com');
print(response.data);import 'package:dio/dio.dart';
Response response;
Dio dio = new Dio();
response = await dio.get("/test?id=12&name=wendu");
print(response.data.toString());
// Optionally the request above could also be done as
response = await dio.get("/test", queryParameters: {"id": 12, "name": "wendu"});
print(response.data.toString());response = await dio.post("/test", data: {"id": 12, "name": "wendu"});response = await Future.wait([dio.post("/info"), dio.get("/token")]);response = await dio.download("https://www.google.com/", "./xx.html");Dio dio = new Dio(); // with default Options
// Set default configs
dio.options.baseUrl = "https://www.xx.com/api";
dio.options.connectTimeout = 5000; //5s
dio.options.receiveTimeout = 3000;
// or new Dio with a BaseOptions instance.
BaseOptions options = new BaseOptions(
baseUrl: "https://www.xx.com/api",
connectTimeout: 5000,
receiveTimeout: 3000,
);
Dio dio = new Dio(options);

import 'package:flutter_test/flutter_test.dart';
import 'package:techamp_flutter_shopping_app/app.dart';
void main() {
group('Product', () {
test('fromJson should return Product with correct values', () {
final json = {
'id': 1,
'price': 109.95,
'title': 'Fjallraven',
'description': 'description',
'category': 'men clothing',
'image': 'image',
};
final actual = Product.fromJson(json);
expect(actual.id, 1);
expect(actual.price, 109.95);
expect(actual.image, 'image');
expect(actual.description, 'description');
expect(actual.category, const MenClothingProductCategory());
});
});
}import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:techamp_flutter_shopping_app/app.dart';
class ProductsScreen extends StatelessWidget {
const ProductsScreen({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => getIt<ProductsCubit>()..getAll(),
child: Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.person, color: Colors.white),
onPressed: () => context.navigator.push(Routes.profileScreen),
),
),
floatingActionButton: const _CartButton(),
body: Container(),
),
);
}
}class _CartButton extends StatelessWidget {
const _CartButton({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocBuilder<CartCubit, CartState>(
builder: (_, state) {
final count = state.carts.totalProductsQuantity;
return Stack(
overflow: Overflow.visible,
children: [
FloatingActionButton(
onPressed: () => context.navigator.push(Routes.cartScreen),
child: const Icon(Icons.shopping_cart),
),
if (count > 0)
Positioned(
top: -4,
right: -2,
child: CircleAvatar(
radius: 12,
backgroundColor: Colors.white,
child: Text('$count'),
),
)
],
);
},
);
}
}class _ProductsErrorWidget extends StatelessWidget {
final String error;
const _ProductsErrorWidget({Key key, @required this.error})
: assert(error != null),
super(key: key);
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
error,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headline6,
),
const SizedBox(height: 16),
Align(
child: RaisedButton(
onPressed: context.read<ProductsCubit>().refresh,
child: const Text('Retry'),
),
),
],
);
}
}dev_dependencies:
flutter_test:
sdk: flutterdependencies:
get_it: ^6.0.0import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:techamp_flutter_shopping_app/app.dart';
void main() {
group('ProductsScreen', () {
ProductsCubit productsCubit;
setUp(() {
productsCubit = _MockProductsCubit();
getIt.registerFactory(() => productsCubit);
});
tearDown(() {
productsCubit?.close();
getIt.reset();
});
testWidgets('should show progress indicator on initial view',
(tester) async {
when(productsCubit.state).thenReturn(const InitialProductsState());
await tester.pumpWidget(
const MaterialApp(
home: ProductsScreen(),
),
);
expect(find.byType(CircularProgressIndicator), findsOneWidget);
});
testWidgets('should show products when loaded successfully',
(tester) async {
const products = [
Product(
id: 1,
title: 'Fjallraven',
image: 'https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg',
price: 109.95,
description: 'Your perfect pack for everyday use.',
category: MenClothingProductCategory(),
),
Product(
id: 2,
title: 'Mens Casual Premium T-Shirts',
image:
'https://fakestoreapi.com/img/71-3HjGNDUL._AC_SY879._SX._UX._SY._UY_.jpg',
price: 22.5,
description: 'Slim-fitting style.',
category: MenClothingProductCategory(),
)
];
when(productsCubit.state)
.thenReturn(const ProductsLoadedState(products: products));
// Fixes Image.network network exception
HttpOverrides.global = null;
await tester.pumpWidget(
const MaterialApp(
home: ProductsScreen(),
),
);
expect(find.text(products.first.title), findsOneWidget);
expect(find.text('${products.first.price} ETB'), findsOneWidget);
expect(find.text(products.last.title), findsOneWidget);
expect(find.text('${products.last.price} ETB'), findsOneWidget);
});
});
}
class _MockProductsCubit extends Mock implements ProductsCubit {}
import 'package:dio/dio.dart';
import 'package:get_it/get_it.dart';
import 'package:techamp_flutter_shopping_app/app.dart';
GetIt getIt = GetIt.instance;
void registerDependencies() {
_registerConfigurations();
_registerProduct();
_registerProfile();
_registerCart();
}
void _registerCart() {
getIt.registerFactory<CartCubit>(() => CartCubit());
}
void _registerConfigurations() {
getIt.registerSingleton(
Dio(
BaseOptions(
baseUrl: 'https://fakestoreapi.com/',
connectTimeout: 20000,
receiveTimeout: 30000,
sendTimeout: 30000,
),
),
);
}
void _registerProduct() {
getIt
..registerFactory<ProductRepository>(() => ProductRepositoryImpl(getIt()))
..registerFactory(() => ProductsCubit(getIt()));
}
void _registerProfile() {
getIt
..registerFactory<ProfileRepository>(() => ProfileRepositoryImpl(getIt()))
..registerFactory(() => ProfileCubit(getIt()));
}

class ProductsScreen extends StatelessWidget {
const ProductsScreen({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => getIt<ProductsCubit>()..getAll(),
child: Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.person, color: Colors.white),
onPressed: () => context.navigator.push(Routes.profileScreen),
),
),
floatingActionButton: const _CartButton(),
body: SafeArea(
child: Builder(
builder: (context) => RefreshIndicator(
onRefresh: context.read<ProductsCubit>().refresh,
child: BlocBuilder<ProductsCubit, ProductsState>(
buildWhen: (previous, current) => previous.maybeWhen(
loaded: (products) => false,
orElse: () => true,
),
builder: (context, state) => state.maybeWhen(
error: (error) => _ProductsErrorWidget(error: error),
loaded: (products) => _ProductsGridView(products: products),
orElse: () => const Center(
child: CircularProgressIndicator(),
),
),
),
),
),
),
),
);
}
}