Flutter: виджет динамического списка

Теперь, когда мы умеем читать данные из сети, сохранять их локально, осталось научится отображать их в виде красивого списка на отдельном экране. Создадим отдельный файл tmclist.dart:

import 'package:flutter/material.dart';
import 'package:invent/globals.dart' as globals;

class TMCList extends StatefulWidget {
  _TMCListState createState() => _TMCListState(); // сюда передаем текущее состояние страницы
}
class _TMCListState extends State<TMCList> {// _ впереди класса, означает чтоб скрыть доступ из другх файлов
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Список ТМЦ')),
      body: Center()
}
      )
    );
  }
}

И далее в body вставляем виджет ListView.builder который может строить список на основе списка данных. В простейшем случае это было бы:

body: ListView.builder(
            padding: const EdgeInsets.all(8),
            itemCount: users.length,
            itemBuilder: (BuildContext context, int index) {
              return Container(
                padding: EdgeInsets.symmetric(vertical: 10),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(globals.tmclist[index]["name"],, style: TextStyle(fontSize: 22)),
                  ],
                )
              );
            }
        ),

Но так не интересно, просто список без «украшений», потому я чкть усложнил, но суть остается едина:

 body: ListView.builder(
        padding: const EdgeInsets.all(8),
        itemCount: globals.tmclist.length,
        itemBuilder: (BuildContext context, int index) {
          return Container(
            padding: EdgeInsets.symmetric(vertical: 10),
            child: Column(
              mainAxisSize: MainAxisSize.max,
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                Align(
                  alignment: AlignmentDirectional(0, 0),
                  child: Row(
                    mainAxisSize: MainAxisSize.max,
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Align(
                        alignment: AlignmentDirectional(0.05, 0),
                        child: Text(
                          globals.tmclist[index]["id"],
                          textAlign: TextAlign.start,
                        ),
                      ),
                      Text(
                        globals.tmclist[index]["name"],
                        textAlign: TextAlign.start,
                        style: TextStyle(fontSize: 22)
                      ),
                      Image.network(
                        'https://picsum.photos/seed/49/600',
                        width: 100,
                        height: 100,
                        fit: BoxFit.cover,
                      ),
                      new Divider()
                    ],
                  ),
                ),
                Text(
                    globals.tmclist[index]["comment"],
                    style: TextStyle(fontSize: 18)
                ),
              ],
            ),
          );
        }
      )

Результат:

Остался вопрос, как навесить действие на выбор какого то пункта, и распознать какой собственно пункт выбрали? А вот для этого служит специальный виджет GestureDetector, который собственно это всё и позволяет делать:

  appBar: AppBar(title: Text('Список документов инвентаризации')),
        body: ListView.builder(
            padding: const EdgeInsets.all(8),
            itemCount: globals.MyInvents.length,
            itemBuilder: (BuildContext context, int index) {
              return
                GestureDetector(
                      onTap: (){
                        print(index);
                      },
                      child: Container(
                      padding: EdgeInsets.symmetric(vertical: 10),
                      child: Column(
                        mainAxisSize: MainAxisSize.max,
                        mainAxisAlignment: MainAxisAlignment.spaceAround,
                        children: [
...

Получим при клике по пунктам меню:

Flutter: Хранение данных приложения

На этом месте должна была быть статья по работу Flutter с SQLite, но не сложилось. Не умеет Flutter без плясок с бубном работать с SQLite одновременно на всех целевых платформах сразу: WIndows/Android/IOS. Самый популярный пакет sqflite — работает только на Android/IOS, остальные, задекларированные как рабочие под Windows, по оной так запустить мне и не удалось, с учётом что у меня ограниченные права Windows. Потому, стал смотреть в сторону пакета Hive — работающего на всех целевых платформах, т.к. написан изначально на языке Dart.

Hive -это быстрая локальная база для Flutter, Dart работающая на основе ключ-значение.

Установка: добавить в зависимости pubsec.yaml:

dependencies:
  hive: ^2.2.3

Для удобства можно написать маленький класс для работы с ключами-значениями:

import 'dart:io';
import 'package:hive/hive.dart';

class THave {
  String bname="";
  void Init(String basename)  {
    var path = Directory.current.path;
    Hive.init(path);
    bname=basename;
  }
  void Add(String key,var value) async {
    var box = await Hive.openBox(bname);
    await box.put(key, value);
  }
  void Delete(String key) async {
    var box = await Hive.openBox(bname);
    await box.delete(key);
  }
  dynamic  Get(String key) async  {
    var box =  await Hive.openBox(bname);
    return (box.get(key));
  }
  Future <Iterable> GetKeys() async {
    var box = await Hive.openBox(bname);
    return await box.keys;
  }
}

И теперь например можно при новом запуске приложения «вспоминать» авторизацию:

main.dart:

void main() {
  THave have=THave();
  have.Init("auth");
  have.Get("UserId").then((UserId){
    if (UserId!=null){
      globals.UserId=UserId;
    };
    have.Get("UserName").then((UserName){
      if (UserId!=null){
        globals.UserName=UserName;
        globals.is_login=true;
      };
      runApp(const MyApp());
    });
  });
}

login.dart:

                      onPressed: (){
                        http.post(Uri.parse('https://erfwe.ru/1.php'),body: {'login':_controller1.text,'password':_controller2.text}).then((response) {
                          print("Response status: ${response.statusCode}");
                          print("Response body: ${response.body}");
                          Map<String, dynamic> user = jsonDecode(response.body);
                          setState(() {
                            globals.UserName = user["UserName"];
                            globals.is_login = true;
                            globals.UserId = user["UserId"];
                            THave have=THave();
                            have.Init("auth");
                            have.Add("UserName", globals.UserName);
                            have.Add("UserId", globals.UserId);
                          });
                        }).catchError((error){
                          print("Error: $error");
                        });
                      },

Причем нужно отметить свойства сохранения и извлечения ключ-значение: при сохранении мы не ждем результата записи. При чтении — соответственно необходимо дождаться.

Ошибка: as been blocked by CORS policy

В одной из задач возникла проблема: скрипт javascript «крутящийся» на localhost должен утянуть файл json с удаленного ресурса https. Вот тут и возникает ошибка:

Error: XMLHttpRequest error. As been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource

Возникает она из-за политик CORS удаленного сайта.

Решение: добавить в скрипт который «выдает» данные, заголовок, что конкретно эти данные можно отдавать ну например ..всем.

<?php
header('Access-Control-Allow-Origin: *');
echo '{"UserId":"10","UserName":"Vasya Pukin 1"}';
?>

Flutter: переопределение кнопки «назад» в AppBar

Возникла ситуация: на одной из страниц (например авторизация) произошли изменения, касающиеся «стартового» экрана с изменением пунктов меню в drawer, ну и его шапке соотвественно. Но возник нюанс — при нажатии кнопки «назад» в AppBar, перерисовка меню не происходит.

Что будем делать? Одним из вариантов решения проблемы является перехват нажатия кнопки при помощи виджета WildPopScope и принудительная перерисовка при помощи вызова роутинга:

@override
  Widget build(BuildContext context) {
    // Переопределяем кнопку назад
    return WillPopScope(
      onWillPop: () {
        Navigator.pushNamed(context,'/');
        return Future.value(false);
      },
      child: Scaffold(
        appBar: AppBar(title: Text('Авторизация')),
        body: Center(
...

Flutter: работа с json

В предыдущей статье мы получили по URL json данные. Теперь задача их обработать.

{
"UserId":10,
"UserName":"Vasya Pukin"
}

Для работы с json, необходимо импортировать библиотеку:

import 'dart:convert';

Далее загрузим данные в тип Map:

 Map<String, dynamic> user = jsonDecode(response.body);
                        print (user["UserName"]);

А что если это массив?

[
  {"UserId":10,"UserName":"Vasya Pukin 1"},
  {"UserId":10,"UserName":"Vasya Pukin 2"}
  ]

В этом случае загрузим JSON в объет List:

                        List users = jsonDecode(response.body);
                        for(int i=0; i < users.length; i++){
                          print(users[i]["UserName"]);
                        }

Обратная конвертация:

                        String json = jsonEncode(user);
                        print (json);
1 67 68 69 70 71 308