Flutter: реализуем выплывающее меню слева

Меню слева в Flutter реализуется при помощи виджета drawer. Заголовок меню отображается при помощи виджета DrawerHeader, а пункты меню при помощи ListTiles.

В результате добавления к предыдущему примеру (удалите leading: new Icon(Icons.menu), ) кода:

 drawer: new Drawer(
          child: new ListView(
            padding: EdgeInsets.zero,
            children: <Widget>[
              new DrawerHeader(
                  child: new Text("Инвентаризация ТМЦ",textAlign: TextAlign.center,style: Theme.of(context).textTheme.headline4),
                  decoration: new BoxDecoration(color: Colors.blue),
              ),
              ListTile(
                title: const Text('Загрузить список',style: TextStyle(color: Colors.green,fontSize: 26, backgroundColor: Colors.amberAccent),),
                onTap: () {},
              ),
              ListTile(title: const Text('Выгрузить результаты',style: TextStyle(fontSize: 26),),onTap: () {}),
              ListTile(title: const Text('Сканировать',style: TextStyle(fontSize: 26),),onTap: () {}),
            ],
          ),

Получим нечто вроде:

Обработка выбора пункта меню происходит в onTap(){} Например закрыть список можно:

 Navigator.pop(context);

Пример как сделать в шапке меню две и более мтрочки:

              new DrawerHeader(
                  child: Column(
                    children: <Widget>[
                        new Text("Инвентаризация ТМЦ",textAlign: TextAlign.center,style: Theme.of(context).textTheme.headline4),
                        new Text("Меня зовут Вася",textAlign: TextAlign.center,style: Theme.of(context).textTheme.headline5),
                      ],
                  ),
              ),

Разделение пунктов меню:

              ListTile(title: const Text('Выгрузить результаты',style: TextStyle(fontSize: 26),),onTap: () {}),
              Divider(color: Colors.black87),
              ListTile(title: const Text('Сканировать',style: TextStyle(fontSize: 26),),onTap: () {}),

Добавить фото в заголовок меню:

 new DrawerHeader(
                decoration: BoxDecoration(
                  image: DecorationImage(
                    fit: BoxFit.scaleDown,
                    image: AssetImage("./lib/images/lenin.jpg"),
                  ),
                ),
                  child: Column(
                    children: <Widget>[
                        new Text("Инвентаризация ТМЦ",textAlign: TextAlign.center,style: Theme.of(context).textTheme.headline4),
                        new Text("Меня зовут Вася",textAlign: TextAlign.center,style: Theme.of(context).textTheme.headline5),
                      ],
                  ),
              ),

Может выйти ошибка:

The following assertion was thrown resolving an image codec:
Unable to load asset: ./lib/resources/logo_1200_630.jpg

Для того чтобы исправить, нужно в pubspec.yaml добавить:

В результате получится что-то вроде:

P.S. Открыл для себя https://app.flutterflow.io где UI можно рисовать «визульно», и на выходе получать код.Это таки несколько удобнее и быстрее. Далее буду пытаться использовать этот сервис

Flutter: учимся создавать AppBar

Любое приложение чаще всего запускается с главного экрана, на котором расположено некое меню. С него и начнем. Возьмём приложение из предыдущей статьи, и продолжим «стругать»:

import 'package:flutter/material.dart';
class Page1 extends StatefulWidget {
  _Page1State createState() => _Page1State(); // сюда передаем текущее состояние страницы
}
class _Page1State extends State<Page1> { // _ впереди класса, означает чтоб скрыть доступ из другх файлов
  @override
  Widget build(BuildContext context) {
    return Scaffold( // чаще всего используемый "корневой" виджет, который позволяет добавлять в себя другие: AppBar, BottomNavigationBar,Drawer, FloatingActionButton и т.п.
        appBar: AppBar(
            title: Text("Инвентаризация"),
            leading: new Icon(Icons.menu),
            actions: <Widget>[
              new IconButton(
                  onPressed: (){},
                  icon: new Icon(Icons.exit_to_app)
              )
            ]
        ),
        body: Center(
            child: Text(
              "Ууу как всё запущено пациент..",
              textAlign: TextAlign.center,
              style: Theme.of(context).textTheme.headline3,
            )
        )
    );
  }

}

Здесь мы добавили иконку развертываемого меню в левом углу и виджет-кнопку «Выхода» из приложения.

Чтобы реализовать выход из приложения, нужно добавить импорт библиотеки и в OnPressed собственно команду выхода:

...
import 'package:flutter/services.dart';
...
                  onPressed: () {
                                        if (Platform.isAndroid) {
                      SystemChannels.platform.invokeMethod(
                          'SystemNavigator.pop');
                    } else {
                      exit(0);
                    };
                  },

Как видите, тут мы уже подобрались к толике кросплатформенности. Что работает на одной платформе, может не работать на другой. И функционал «выхода» в одной из этих специфик.

Flutter: основные принципы построения приложения

Минимальное приложение Flutter, это:

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

void main() {
  runApp(const MyApp());
}
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
debugShowCheckedModeBanner: false, //убираем надпись debug
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),

    );
  }
}

,далее обычно начинают плясать именно от этого шаблона. Запускаем, смотрим…и ничего кроме квадратика с названием окна не видим. Ну собственно ничего более и нет.

Одной из «киллер фич» Flutter считается, что всё что визуально расположено на экране — это виджеты вложенные друг в друга. С «детьми» и «родителями». Принцип разметки примерно такой же как в HTML + CSS: кидаем в «стартовый» виджет другие виджеты различного вида , и добавляем к ним «украшения». В данном случае внутри стартового виджета ничего нет.

Второй «киллер фичей» флаттера является так называемый HotReload — при изменении кода программы, и последующем его изменении, запущеная ранее программа не закрывается, а «обновляется» на лету. Если конечно код не изменен «кардинально»

Добавляем файл-основной экран page1.dart:

import 'package:flutter/material.dart';
class Page1 extends StatefulWidget {
  _Page1State createState() => _Page1State(); // сюда передаем текущее состояние страницы
}
class _Page1State extends State<Page1> { // _ впереди класса, означает чтоб скрыть доступ из другх файлов
  @override
  Widget build(BuildContext context) {
    return Scaffold( // чаще всего используемый "корневой" виджет, который позволяет добавлять в себя другие: AppBar, BottomNavigationBar,Drawer, FloatingActionButton и т.п.
        appBar: AppBar(title: Text("Home page")),
        body: Center(
            child: Text(
              "Ууу как всё запущено пациент..",
              textAlign: TextAlign.center,
              style: Theme.of(context).textTheme.headline3,
            )
        )
    );
  }

}

И далее добавляем в main,dart строчки:

В результате запуска получаем что-то типа:

Установка и настройка Flutter

За сим открываю цикл статей по использованию платформы для разработки кросс платформенных приложений с использованием Flutter и соответственно языка Dart.

Итак, минимальный старт для начала разработки следующий:

  1. Устанавливаем Dart
  2. Устанавливаем Android Studio
  3. Устанавливаем Flutter
  4. Настраиваем Android Studio для работы с Flutter и Dart

1. Установка Dart

Заходим на https://dart.dev/get-dart/archive , скачиваем последний релиз, распаковываем в папку.

2. Скачиваем, устанавливаем Android Studio.

3. Заходим на https://docs.flutter.dev/get-started/install, скачиваем и распаковываем последний релиз Flutter

Настройка Flutter пока заключается в том, чтобы зайти в папку E:\flutter\, и запустить из командной строки

flutter_console.bat
flutter doctor

Вы должны увидеть что-то типа:

Visual Studio — может использоваться вместо Android Studio. На Android Studio скорее всего у вас будет «крестик», т.е. он не настроен. Собственно далее необходимо перейти к п.4 — настройка Android Studio

4. Настройка Android Studio

Первым делом необходимо установить плагины: File -> Settings -> Plugins

После чего, перезагрузите Android Studio и снова зайдите в консоль Flutter:

flutter_console.bat
flutter doctor --android-licenses

И снова заходим в Android Studio и создаем проект на Flutter, предварительно выставив где находится flutter и dart:

Будет создан каркас приложения с минимальным функционалом для запуска. Выберем целевую платформу, и запустим

RabbitMQ: Отложеная доставка и время жизни пакета

Собрал наконец в одном классе, всё что мне нужно от RabbitMQ: а именно отправка сообщения в очередь, с отложеной доставкой, плюс уничтожение сообщения по прошествии какого то времени «не доставки».

Ну да, казалось бы элементарные вещи, но на PHP примеров для этого случая не сказать что уж слишком много как оказалось..

<?php

//Необходимые классы
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Wire\AMQPTable;


class TRabbit extends TLog{ 
    public $host="";
    public $login="";
    public $password="";
    public $port="";
    public $connection=null;
    public static $isInstance=false; 
    // настройки касающиеся очереди
    public $passive=false;      // может использоваться для проверки того, инициирован ли обмен, без того, чтобы изменять состояние сервера
    public $durable=true;       // убедимся, что RabbitMQ никогда не потеряет очередь при падении - очередь переживёт перезагрузку брокера
    public $exclusive=false;    // используется только одним соединением, и очередь будет удалена при закрытии соединения
    public $autodelete=false;   // очередь удаляется, когда отписывается последний подписчик
    public $ttl=3000;           // время жизни пакета, через которое оно при недоставке дропнется,в милисекундах
            
    public function __destruct(){
        if (self::$isInstance==false) {        
          $this->connection->close();  
        }
    }    
    public function __construct(){
        self::$isInstance=true;
        $this->host= TConfig::GetConfigKeyValue("rabbitmq_host");
        $this->login= TConfig::GetConfigKeyValue("rabbitmq_login");
        $this->host= "localhost";
        
        $this->password= TConfig::GetConfigKeyValue("rabbitmq_password");
        $this->password="guest";
        $this->port= TConfig::GetConfigKeyValue("rabbitmq_port");        
        $this->connection = new AMQPStreamConnection($this->host, $this->port, $this->login, $this->password);        
    }    
    /**
     * Поставить в очередь сообщение
     * @global type $db
     * @param type $queue   - имя очереди
     * @param type $message - сообщение в json формате
     * @param type $delay   - отсрочка доставки в секундах (необязательно)
     * @param type $ttl     - время жизни пакета, через которое оно при недоставке дропнется
     * @return type
     */
    public function  Producer($queue,$message,$delay=0,$ttl=null) {  
     global $db;
        try {    
          $answer = AnswerStrucNext(false, "Failed to queue a RabbitMQ message");             
             $channel = $this->connection->channel();      
            $channel->exchange_declare(
                'delay',
                'x-delayed-message',
                false,            /* не создается очереь если нет её */
                $this->durable,   /* гарантированная доставка */
                false,            /* удаление очереди если пустая и нет слушателей */
                false,            /* internal */
                false,            /* не ждать ответа от сервера (т. е. продолжать получение) */
                [
                    'x-delayed-type' => ['S', 'direct']                    
                ]);
             if (is_null($ttl)) $ttl=$this->ttl;
             $channel->queue_declare($queue, $this->passive, $this->durable, $this->exclusive, $this->autodelete,false,
                array(
                        "x-message-ttl" => array("I", $ttl)
                    )                     
                );
             $channel->queue_bind($queue, "delay", $queue);
               if ($delay==0){
                  $msg = new AMQPMessage($message);		        
               } else {
                   $msg = new AMQPMessage($message, ['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]);		
                   $msg->set('application_headers', new AMQPTable(['x-delay' => $delay*1000]));        
               };
          $channel->basic_publish($msg,"delay",$queue);                  
          $channel->close();
          $answer = AnswerStrucNext(false, "ok");             
       } catch(Exception $err) {
           $this->InsertLog(TLog::PT_PLAIN,"Ошибка постановки в очередь $queue (RabbitMQ)", TLog::R_Error, $message);
       };          
     return $answer;
    }
}
1 70 71 72 73 74 308