Loading...
Loading...
Compare original and translation side by side
One Codebase for Client and Sever.
客户端与服务端共享单一代码库
import 'package:flutter/material.dart';
import 'plugins/desktop/desktop.dart';
import 'ui/home/screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: HomeScreen(),
);
}
}import 'package:flutter/material.dart';
import '../counter/screen.dart';
import '../todo/screen.dart';
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _currentIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: <Widget>[
CounterScreen(),
TodosScreen(),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (val) {
if (mounted)
setState(() {
_currentIndex = val;
});
},
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.add),
title: Text('Counter'),
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
title: Text('Todos'),
),
],
),
);
}
}import 'package:flutter/material.dart';
import 'plugins/desktop/desktop.dart';
import 'ui/home/screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: HomeScreen(),
);
}
}import 'package:flutter/material.dart';
import '../counter/screen.dart';
import '../todo/screen.dart';
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _currentIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: <Widget>[
CounterScreen(),
TodosScreen(),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (val) {
if (mounted)
setState(() {
_currentIndex = val;
});
},
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.add),
title: Text('Counter'),
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
title: Text('Todos'),
),
],
),
);
}
}import 'package:flutter/material.dart';
import 'package:shared_dart/src/models/counter.dart';
class CounterScreen extends StatefulWidget {
@override
_CounterScreenState createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
CounterModel _counterModel = CounterModel();
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counterModel.add();
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyCounterPage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text('Counter Screen'),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'${_counterModel.count}',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}CounterModelclass CounterModel {
CounterModel();
int _count = 0;
int get count => _count;
void add() => _count++;
void subtract() => _count--;
void set(int val) => _count = val;
}import 'package:flutter/material.dart';
import 'package:shared_dart/src/models/counter.dart';
class CounterScreen extends StatefulWidget {
@override
_CounterScreenState createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
CounterModel _counterModel = CounterModel();
void _incrementCounter() {
setState(() {
// 调用setState会通知Flutter框架状态发生了变化,导致build方法重新执行
// 这样界面就能反映更新后的值。如果我们修改_counter但不调用setState(),
// build方法不会重新执行,界面也不会有任何变化。
_counterModel.add();
});
}
@override
Widget build(BuildContext context) {
// 每次调用setState时都会重新执行这个方法,比如上面的_incrementCounter方法
// Flutter框架已经过优化,重新执行build方法的速度很快,所以你可以重建所有需要更新的部分
// 而不需要单独修改widget实例。
return Scaffold(
appBar: AppBar(
// 这里我们使用App.build方法创建的MyCounterPage对象的值来设置appbar标题
title: Text('Counter Screen'),
),
body: Center(
// Center是一个布局widget,它将子widget放置在父widget的中间
child: Column(
// Column也是一个布局widget,它将子widget垂直排列。默认情况下,它会根据子widget的宽度调整自身宽度,
// 并尽可能占据父widget的高度。
//
// 启用"调试绘制"(在控制台按"p",在Android Studio的Flutter Inspector中选择"Toggle Debug Paint",
// 或在Visual Studio Code中执行"Toggle Debug Paint"命令)可以查看每个widget的线框。
//
// Column有很多属性可以控制自身的大小和子widget的位置。这里我们使用mainAxisAlignment让子widget垂直居中;
// 因为Column是垂直排列的,所以主轴是垂直方向(交叉轴是水平方向)。
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'你已点击按钮的次数:',
),
Text(
'${_counterModel.count}',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // 这个尾随逗号让build方法的自动格式化效果更好
);
}
}CounterModelclass CounterModel {
CounterModel();
int _count = 0;
int get count => _count;
void add() => _count++;
void subtract() => _count--;
void set(int val) => _count = val;
}import 'package:flutter/material.dart';
import '../../src/classes/todo.dart';
import '../../src/models/todos.dart';
class TodosScreen extends StatefulWidget {
@override
_TodosScreenState createState() => _TodosScreenState();
}
class _TodosScreenState extends State<TodosScreen> {
final _model = TodosModel();
List<ToDo> _todos;
@override
void initState() {
_model.getList().then((val) {
if (mounted)
setState(() {
_todos = val;
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Todos Screen'),
),
body: Builder(
builder: (_) {
if (_todos != null) {
return ListView.builder(
itemCount: _todos.length,
itemBuilder: (context, index) {
final _item = _todos[index];
return ListTile(
title: Text(_item.title),
subtitle: Text(_item.completed ? 'Completed' : 'Pending'),
);
},
);
}
return Center(
child: CircularProgressIndicator(),
);
},
),
);
}
}// To parse this JSON data, do
//
// final toDo = toDoFromJson(jsonString);
import 'dart:convert';
List<ToDo> toDoFromJson(String str) => List<ToDo>.from(json.decode(str).map((x) => ToDo.fromJson(x)));
String toDoToJson(List<ToDo> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class ToDo {
int userId;
int id;
String title;
bool completed;
ToDo({
this.userId,
this.id,
this.title,
this.completed,
});
factory ToDo.fromJson(Map<String, dynamic> json) => ToDo(
userId: json["userId"],
id: json["id"],
title: json["title"],
completed: json["completed"],
);
Map<String, dynamic> toJson() => {
"userId": userId,
"id": id,
"title": title,
"completed": completed,
};
}import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_dart/src/classes/todo.dart' as t;
class TodosModel {
final kTodosUrl = '[https://jsonplaceholder.typicode.com/todos'](https://jsonplaceholder.typicode.com/todos');
Future<List<t.ToDo>> getList() async {
final _response = await http.get(kTodosUrl);
if (_response != null) {
final _todos = t.toDoFromJson(_response.body);
if (_todos != null) {
return _todos;
}
}
return [];
}
Future<t.ToDo> getItem(int id) async {
final _response = await http.get('$kTodosUrl/$id');
if (_response != null) {
final _todo = t.ToDo.fromJson(json.decode(_response.body));
if (_todo != null) {
return _todo;
}
}
return null;
}
}import 'package:flutter/material.dart';
import '../../src/classes/todo.dart';
import '../../src/models/todos.dart';
class TodosScreen extends StatefulWidget {
@override
_TodosScreenState createState() => _TodosScreenState();
}
class _TodosScreenState extends State<TodosScreen> {
final _model = TodosModel();
List<ToDo> _todos;
@override
void initState() {
_model.getList().then((val) {
if (mounted)
setState(() {
_todos = val;
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Todos Screen'),
),
body: Builder(
builder: (_) {
if (_todos != null) {
return ListView.builder(
itemCount: _todos.length,
itemBuilder: (context, index) {
final _item = _todos[index];
return ListTile(
title: Text(_item.title),
subtitle: Text(_item.completed ? '已完成' : '待处理'),
);
},
);
}
return Center(
child: CircularProgressIndicator(),
);
},
),
);
}
}// 要解析此JSON数据,请执行以下操作
//
// final toDo = toDoFromJson(jsonString);
import 'dart:convert';
List<ToDo> toDoFromJson(String str) => List<ToDo>.from(json.decode(str).map((x) => ToDo.fromJson(x)));
String toDoToJson(List<ToDo> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class ToDo {
int userId;
int id;
String title;
bool completed;
ToDo({
this.userId,
this.id,
this.title,
this.completed,
});
factory ToDo.fromJson(Map<String, dynamic> json) => ToDo(
userId: json["userId"],
id: json["id"],
title: json["title"],
completed: json["completed"],
);
Map<String, dynamic> toJson() => {
"userId": userId,
"id": id,
"title": title,
"completed": completed,
};
}import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_dart/src/classes/todo.dart' as t;
class TodosModel {
final kTodosUrl = 'https://jsonplaceholder.typicode.com/todos';
Future<List<t.ToDo>> getList() async {
final _response = await http.get(kTodosUrl);
if (_response != null) {
final _todos = t.toDoFromJson(_response.body);
if (_todos != null) {
return _todos;
}
}
return [];
}
Future<t.ToDo> getItem(int id) async {
final _response = await http.get('$kTodosUrl/$id');
if (_response != null) {
final _todo = t.ToDo.fromJson(json.decode(_response.body));
if (_todo != null) {
return _todo;
}
}
return null;
}
}Now time for the magic..
undefined现在到了最神奇的部分..
undefined
Create another file at the root called service.yaml and add the following:
Replace PROJECT\_NAME with your project name, mine is shared-dart for this example.
You will also need to replace YOUR\_DOCKER\_NAME with your docker username so the container can be deployed correctly.
Update your pubspec.yaml with the following:
The important package here is shelf as it allows us to run a http server with dart.
Create a folder in the root of the project called bin then add a file server.dart and replace it with the following:
This will tell the container what port to listen for and how to handle the requests.
Create a folder src in the bin folder and add a file routing.dart and replace the contents with the following:
There is still nothing imported from our main project but you will start to see some similarities. Here we specify controllers for todos and counter url paths.
that means any url with the following:[https://mydomain.com/todos](https://mydomain.com/todos) , [https://mydomain.com/todos](https://mydomain.com/todos)/1
will get routed to the TodoController to handle the request.
> This is also the first time I found out about FutureOr. It allows you to return a sync or async function.
And important part about build a REST API is having a consistent response body, so here we can create a wrapper that adds fields we always want to return, like the status of the call, a message and the body.
Create a file at src/result.dart and add the following:
This will always return json and the fields that we want to show. You could also include your paging meta data here.
Create a file in at the location src/controllers/counter.dart and add the following:
You will see the import to the lib folder of the root project. Since it shares the pubspec.yaml all the packages can be shared. You can import the CounterModel that we created earlier.
Create a file in at the location src/controllers/todos.dart and add the following:
Just like before we are importing the TodosModel model from the lib folder.
For convenience add a file at the location src/controllers/index.dart and add the following:
This will make it easier to import all the controllers.
在根目录创建另一个文件service.yaml并添加以下内容:
将PROJECT_NAME替换为你的项目名称,本文示例中我的项目名称是shared-dart。
你还需要将YOUR_DOCKER_NAME替换为你的Docker用户名,这样容器才能正确部署。
更新pubspec.yaml文件,添加以下内容:
这里最重要的包是shelf,它允许我们用Dart运行HTTP服务。
在项目根目录创建bin文件夹,然后添加server.dart文件并替换为以下内容:
这会告诉容器要监听哪个端口以及如何处理请求。
在bin文件夹中创建src文件夹,添加routing.dart文件并替换内容为以下代码:
现在还没有从主项目中导入任何内容,但你会发现一些相似之处。这里我们为todos和counter路径指定了控制器。
这意味着任何类似https://mydomain.com/todos、https://mydomain.com/todos/1的请求都会被路由到TodoController处理。
> 这也是我第一次了解到FutureOr,它允许你返回同步或异步函数。
构建REST API的一个重要部分是拥有一致的响应体,所以我们可以创建一个包装类,添加我们总是想要返回的字段,比如请求状态、消息和响应体。
在src/result.dart创建文件并添加以下内容:
这会始终返回JSON格式的数据以及我们想要展示的字段。你也可以在这里添加分页元数据。
在src/controllers/counter.dart创建文件并添加以下内容:
你会看到这里导入了根项目lib文件夹中的内容。因为共享同一个pubspec.yaml,所以所有包都可以共享。你可以导入我们之前创建的CounterModel。
在src/controllers/todos.dart创建文件并添加以下内容:
和之前一样,我们从lib文件夹中导入了TodosModel。
为了方便使用,在src/controllers/index.dart创建文件并添加以下内容:
这样可以更轻松地导入所有控制器。{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: [https://go.microsoft.com/fwlink/?linkid=830387](https://go.microsoft.com/fwlink/?linkid=830387)
"version": "0.2.0",
"configurations": [
{
"name": "Client",
"request": "launch",
"type": "dart",
"program": "lib/main.dart"
},
{
"name": "Server",
"request": "launch",
"type": "dart",
"program": "bin/server.dart"
}
]
}Keep in mind this is running your Dart code from you lib folder in your Flutter project without needing the Flutter widgets!
{
// 使用IntelliSense了解可能的属性
// 悬停查看现有属性的描述
// 更多信息请访问:[https://go.microsoft.com/fwlink/?linkid=830387](https://go.microsoft.com/fwlink/?linkid=830387)
"version": "0.2.0",
"configurations": [
{
"name": "Client",
"request": "launch",
"type": "dart",
"program": "lib/main.dart"
},
{
"name": "Server",
"request": "launch",
"type": "dart",
"program": "bin/server.dart"
}
]
}请记住,这是直接运行Flutter项目lib文件夹中的Dart代码,不需要Flutter widgets!