Когда скорость имеет решающее значение, когда необходимо иметь возможность обмениваться информацией мгновенно, есть несколько способы добиться этого, отправка множества сетевых запросов на сервер не всегда решает эту задачу, так как часто нам нужно поддерживать постоянное активное соединение с сервером. Тогда и появляются WebSocket.
Представьте себе трубу, по которой вода (данные) может течь в обе стороны.
Преимущества WebSocket над HTTPS
WebSocket имеет множество преимуществ над привычным HTTPS, в зависимости от архитектуры вашего проекта, WebSocket обычно быстрее в обмене информацией.
В отличии от принципа запрос-ответ, как в HTTP, WebSocket предлагает обмен информации в реальном времени с двунаправленной системой передачи данных, в которой нет необходимости отправлять запросы для получения ответов.
Благодаря такому методу мгновенной коммуникации разработчики могут предоставлять пользователям возможность получения уведомлений, оповещений, а также обмен сообщениями и использование других сервисов.
Создание Node сервера
Нам предстоит поработать над двумя приложениями: сервером на Node.js и, собственно, Flutter-приложением. В сущности, наш сервер будет эхо-сервер, который получает данные и отправляет их всем, кто слушает (другими словами - тем, кто подписан на получение этих данных). Условно, сервер является мостом между клиентами.
В роли клиента будет выступать Flutter приложение, которое будет использовать WebSocket для получения информации. Чтобы установить сервер нужно:
Создать папку с именем проекта, например
socket_chat_server
Создать файл
package.json
в этой папкеПереключиться в текущую папку, если еще не сделали это
Запустить команду
npm init
и завершить установку
Измените файл package.json
, чтобы добавить нужные нам две зависимости:
package.json
{
"name": "socket_chat_server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node ./index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"http": "0.0.1-security",
"socket.io": "^2.3.0"
},
"engines": {
"node": "10.x"
}
}
Затем установим npm модули с помощью команды npm install
.
index.js
Последним шагом будет написание логики сервера, как в примере:
const server = require('http').createServer()
const io = require('socket.io')(server)
io.on('connection', function (client) {
console.log('client connect...', client.id);
client.on('typing', function name(data) {
console.log(data);
io.emit('typing', data)
})
client.on('message', function name(data) {
console.log(data);
io.emit('message', data)
})
client.on('location', function name(data) {
console.log(data);
io.emit('location', data);
})
client.on('connect', function () {
})
client.on('disconnect', function () {
console.log('client disconnect...', client.id)
// handleDisconnect()
})
client.on('error', function (err) {
console.log('received error from client:', client.id)
console.log(err)
})
})
var server_port = process.env.PORT || 3000;
server.listen(server_port, function (err) {
if (err) throw err
console.log('Listening on port %d', server_port);
});
Здесь все очень просто, запускается соединение, затем идет прослушивание данных через client.on
, передача события для прослушивания, к примеру "сообщение", и отправка информации назад всем, кто подключен к серверу. Ваш WebSocket должен быть запущен на http://localhost:3000
.
С помощью команды npm start
можно протестировать сервер. Вывод должен быть таким:
Создание приложения Flutter
Flutter приложение будет выступать как клиент, которые получает сообщения от сервера и отправляет их к нему. Создайте новый проект с помощью команды flutter create socket_chat
. На pub.dev есть несколько socket.io пакетов, но, как мне кажется, socket_io_client будет проще для понимания, так как является копией js библиотеки:
Обратите внимание
При подключении к localhost
на Android эмуляторе может потребоваться немного больше настроек. Команда adb reverse tcp:3000 tcp:3000
разрешает подключаться к localhost
через http://127.0.0.1:3000
на Android (а без команды можно подключится к localhost
с Android эмулятора посредством подключения к адресу http://10.0.2.2:3000
. прим. переводчика) . После удаления стандартного приложения-счетчика приступим к написанию home.dart
.
home.dart
...
Socket socket;
@override
void initState() {
super.initState();
connectToServer();
}
void connectToServer() {
try {
// Configure socket transports must be sepecified
socket = io('http://127.0.0.1:3000', <String, dynamic>{
'transports': ['websocket'],
'autoConnect': false,
});
// Connect to websocket
socket.connect();
// Handle socket events
socket.on('connect', (_) => print('connect: ${socket.id}'));
socket.on('location', handleLocationListen);
socket.on('typing', handleTyping);
socket.on('message', handleMessage);
socket.on('disconnect', (_) => print('disconnect'));
socket.on('fromServer', (_) => print(_));
} catch (e) {
print(e.toString());
}
}
// Send Location to Server
sendLocation(Map<String, dynamic> data) {
socket.emit("location", data);
}
// Listen to Location updates of connected usersfrom server
handleLocationListen(Map<String, dynamic> data) async {
print(data);
}
// Send update of user's typing status
sendTyping(bool typing) {
socket.emit("typing",
{
"id": socket.id,
"typing": typing,
});
}
// Listen to update of typing status from connected users
void handleTyping(Map<String, dynamic> data) {
print(data);
}
// Send a Message to the server
sendMessage(String message) {
socket.emit("message",
{
"id": socket.id,
"message": message, // Message to be sent
"timestamp": DateTime.now().millisecondsSinceEpoch,
},
);
}
// Listen to all message events from connected users
void handleMessage(Map<String, dynamic> data) {
print(data);
}
...
Пример выше - простая реализация WebSocket, в которой есть функции прослушивания и отправления событий к серверу.
Настройка CI/CD для тестирования
Возможно, во время разработки вам потребуется использование различных WebSocket для разных версий приложения(Test, Dev, или Prod). Хорошей практикой считается вынесение секретных участков кода(к примеру, URL-адрес хоста) в зашифрованную CI/CD сборку.
Кроме того, использование CI/CD стало нормой для хороших тестируемых и масштабируемых мобильных приложений, и поэтому важно и рекомендуется всегда поддерживать наличие оного для ваших Flutter пактов, Flutter приложений и тому подобного.
Существует множество способов интеграции среды CI/CD в Codemagic. Я хотел бы пойти более интересным, но сложным путем и объяснить, как создать среды dev
и prod
с помощью простого старого .yaml
файла и загрузить его на свой Codemagic.io проект в кратчайшие сроки.
Да, я знаю, что есть UI, но иногда вам нужно протестировать поток CI/CD, не прерывая существующие, или создать новый поток с графическим интерфейсом. Итак, вот быстрый, но простой способ сделать это:
Настройки и Среды
Мы будем создавать нашу структуру CI/CD и среды таким образом, чтобы мы могли использовать как динамические значения из Codemagic, так и фиксированные значения среды из наших настроек.
Структура проста, у нас есть наш ненастроенный JSON файл config/appconfig.json
, в нем будут размещены наши URL-адреса хостов для разработки и готовой сборки. Вам также нужно будет установить его следующим образом.
assets:
- config/app_config.json
Код в lib/config/config_loader.dart
управляет загрузкой данных из нашего JSON файла (config/appconfig.json)
.
env.dart
и env_config.dart
управляют созданием наших различных сред для использования в_dev.dart
и _prod.dart
.
Для большинства Flutter приложений main.dart
является входной точкой. Но для нас, чтобы наша задумка работала, нужно создать файлы main_common.dart
, main_dev.dart
и main_prod.dart
. Как следует из названия, main_common.dart
будет мостом, связывающим с предпочитаемой средой. Теперь для запуска кода нужно будет указывать настройки, с которыми мы хотим запустить приложение.
У нас есть flutter run -t lib/main_dev.dart
для разработки и flutter run -t lib/main_prod.dart
для готовой сборки.
Переменные среды
Теперь нам нужно преобразовать JSON конфиг в base64, чтобы его можно было добавить как переменные среды в codemagic.yaml
, здесь можно найти более подробную информацию об этом.
Codemagic.yaml
Codemagic дает возможность записывать настройки работы приложения в легко настраиваемом yaml
синтаксисе. Мы создадим и изменим наш файл codemagic.yaml
следующим образом:
# Automatically generated on 2020-11-16 UTC from https://codemagic.io/app/5fb2a25c605096f91720b983/settings
# Note that this configuration is not an exact match to UI settings. Review and adjust as necessary.
workflows:
default-workflow:
name: Default Workflow
max_build_duration: 60
environment:
vars:
APP_CONFIG: ewogICJnQXBpS2V5IjogIkFJemFTeUEzbl95bTlWUUU2NURyRUVpdDZobnNtWDgyR3FGb3Q0QSIKfQo=
flutter: stable
xcode: latest
cocoapods: default
scripts:
- |
# set up debug keystore
rm -f ~/.android/debug.keystore
keytool -genkeypair \
-alias androiddebugkey \
-keypass android \
-keystore ~/.android/debug.keystore \
-storepass android \
-dname 'CN=Android Debug,O=Android,C=US' \
-keyalg 'RSA' \
-keysize 2048 \
-validity 10000
- |
# set up local properties
echo "flutter.sdk=$HOME/programs/flutter" > "$FCI_BUILD_DIR/android/local.properties"
- cd . && flutter packages pub get
- |
# Create directory if it doesn't exist
mkdir -p $FCI_BUILD_DIR/config
# Write out the environment variable as a json file
echo $APP_CONFIG | base64 --decode > $FCI_BUILD_DIR/config/app_config.json
- cd . && flutter build apk --debug -t lib/main_dev.dart
- find . -name "Podfile" -execdir pod install \;
- cd . && flutter build ios --debug --no-codesign lib/main_dev.dart
artifacts:
- build/**/outputs/**/*.apk
- build/**/outputs/**/*.aab
- build/**/outputs/**/mapping.txt
- build/ios/ipa/*.ipa
- /tmp/xcodebuild_logs/*.log
- flutter_drive.log
publishing:
email:
recipients:
- ogbondachiziaruhoma@gmail.com
Определив наши переменные среды следующим образом:
vars:
APP_CONFIG: ewogICJkZXZIb3N0IjogIkRldmVsb3BtZW50IEhvc3QiLAogICJwcm9kSG9zdCI6ICJQcm9kdWN0aW9uIEhvc3QiCn0K
Вставив этот фрагмент кода выше, мы можем удалить файл /config/app_config.json
и создать его при запуске CI сборки.
# Write out the environment variable as a json file
echo $APP_CONFIG | base64 --decode > $FCI_BUILD_DIR/config/app_config.json
Примечание: Наш входной файл lib/main_dev.dart
в файле codemagic.yaml
должен быть изменен на lib/main_prod.dart
для готовой сборки.
Запуск сборки
После сохранения codemagic.yaml
файла на предпочитаемой системе контроля версий(GitHub, bitbucket и т.д), нажмите на “Start your first build”.
Файл среды определится автоматически:
Нажмите на “Start new build”:
Вуаля, сборка запущена.
The end
Веб-сокеты быстрые, просты в настройке и все еще могут быть "RESTful". Их можно использовать для большого количества приложений. И мы также видим, что очень просто настроить работу приложения и интегрировать различные переменные среды/скрыть части кода в Codemagic CI/CD, и радость в том, что вам нужно сделать это только один раз, после этой настройки вы можете добавить больше конфигураций или изменить существующие.
Если вы хотите познакомиться с более продвинутым использованием веб-сокетов и Codemagic CI/CD, пройдите по ссылки, чтобы посмотреть полный код.