[Разработка под Android, Dart, Flutter] Основы Flutter для начинающих (Часть V)
    
    
        
    
    
    
    
            
    
        
            
                
                                    
                
                                    
                
                    
                
            
        
    
    
        
            
                
                
                    
                         
                         
                       
                    
                        Автор 
                        Сообщение 
                    
                                        
                        
                            
                                
                                
                                                                                                            news_bot ®
                                                                        
                                                                                                                                                
                                                                            
                                                                                                                
                                            Стаж: 7 лет 8 месяцев                                        
                                                                                                                
                                            Сообщений: 27286                                        
                                                                                                                                                
                                                             
                            
                                
                             
                         
                        
                            
                                
                                    
                                        
                                        
 Наконец-то мы добрались до одной из самых важных тем, без которой идти дальше нет смысла.План довольно простой: нам предстоит познакомиться с клиент-серверной архитектурой и реализовать получение списка постов.В конце мы правильно организуем файлы наших страниц и вынесем элемент списка в отдельный файл.Полетели!Наш план
- Часть 1 - введение в разработку, первое приложение, понятие состояния;
 
- Часть 2 - файл pubspec.yaml и использование flutter в командной строке;
 
- Часть 3 - BottomNavigationBar и Navigator;
 
- Часть 4- MVC. Мы будем использовать именно этот паттерн, как один из самых простых;
 
- Часть 5 (текущая статья) - http пакет. Создание Repository класса, первые запросы, вывод списка постов;
 
- Часть 6 - работа с формами, текстовые поля и создание поста.
 
- Часть 7 - работа с картинками, вывод картинок в виде сетки, получение картинок из сети, добавление своих в приложение;
 
- Часть 8 - создание своей темы, добавление кастомных шрифтов и анимации;
 
- Часть 9 - немного о тестировании;
 
Client и ServerМодель Client / Server лежит в основе всего Интернета и является наиболее распространенной.В чем её суть?Сначала разберемся что такое клиент и сервер:
- Клиент - пользовательское устройство, которое отправляет запросы за сервер и получает ответы. Это может быть смартфон, компьютер или MacBook.
 
- Сервер - специальный компьютер, который содержит данные, необходимые для пользователя.
 
Вся модель сводиться к примитивному принципу: клиент отправил запрос, сервер принял его, обработал и передал ответ клиенту.Для организации взаимодействия сервера и клиента используются специальные протоколы. На текущий момент одним из самых распространенных протоколов в сети Интернет является http / https (s означает защищенный, secure).http / https позволяет передавать почти все известные форматы данных: картинки, видео, текст.Мы будем работать с JSON форматом.JSON - простой и понятный формат данных, а главное легковесный, т.к. передается только текст.Пример JSON:
[
  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  },
  {
    "userId": 1,
    "id": 2,
    "title": "qui est esse",
    "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
  },
  ...
]
Здесь массив постов, который мы будем получать от сервера.Обратите внимание: квадратные скобки указывает на массив данных, а фигурные на отдельный объект.JSON позволяет создавать глубокую вложенность объектов и массивов:
{
  "total_items" : 1
  "result" : [
    {
      "id" : 1,
      "name" : "Twillight Sparkle",
      "pony_type" : "alicorn",
      "friends" : [
        "Starlight Glimmer", "Applejack", "Rarity", "Spike"
      ]
    }
  ]
}
Понятие запросаДля обмена данными клиент должен отправлять запросы на сервер.Т.к. интернет в большинстве случаев использует http / https то запросы называются HTTP запросами.Структура HTTP запроса:
- URL - уникальный адрес в Интернете, который идентифицирует сервер и его конкретный ресурс, данные которого мы собираемся получить. В нашем случае URL выглядит следующим образом: https://jsonplaceholder.typicode.com/posts. (об структуре самого URL'а можно почитать в Википедии)
 
- Метод, который определяет типа запроса. GET используется только для получения данных, POST позволяет клиенту добавить свои данные на сервер, DELETE - удалить их, PUT - изменить.
 
- Данные запроса обычно называются телом запроса и используются совместно с POST, PUT и DELETE методами. Для GET метода в основном используются параметры самого URL'а. Выглядит это следующим образом: https://jsonplaceholder.typicode.com/posts/1 (здесь мы обращаемся к конкретному посту по его id = 1)
 
Запрос и вывод списка постовМы будем использовать довольно мощный и простой пакет http для отправки запросов на сервер.Сначала убедимся, что мы указали его в pubspec.yaml файле:
# блок зависимостей
dependencies:
  flutter:
    sdk: flutter
  # подключение необходимых pub-пакетов
  # используется для произвольного размещения
  # компонентов в виде сетки
  flutter_staggered_grid_view: ^0.4.0
  # мы будем использовать MVC паттерн
  mvc_pattern: ^7.0.0
  # http предоставляет удобный интерфейс для создания
  # запросов и обработки ошибок
  http: ^0.13.3
Переходим к созданию классов модели.Для этого создайте файл post.dart в папке models:
// сначала создаем объект самого поста
class Post {
  // все поля являются private
  // это сделано для инкапсуляции данных
  final int _userId;
  final int _id;
  final String _title;
  final String _body;
  // создаем getters для наших полей
  // дабы только мы могли читать их
  int get userId => _userId;
  int get id => _id;
  String get title => _title;
  String get body => _body;
  // Dart позволяет создавать конструкторы с разными именами
  // В данном случае Post.fromJson(json) - это конструктор
  // здесь мы принимаем JSON объект поста и извлекаем его поля
  // обратите внимание, что dynamic переменная
  // может иметь разные типы: String, int, double и т.д.
  Post.fromJson(Map<String, dynamic> json) :
    this._userId = json["userId"],
    this._id = json["id"],
    this._title = json["title"],
    this._body = json["body"];
}
// PostList являются оберткой для массива постов
class PostList {
  final List<Post> posts = [];
  PostList.fromJson(List<dynamic> jsonItems) {
    for (var jsonItem in jsonItems) {
      posts.add(Post.fromJson(jsonItem));
    }
  }
}
// наше представление будет получать объекты
// этого класса и определять конкретный его
// подтип
abstract class PostResult {}
// указывает на успешный запрос
class PostResultSuccess extends PostResult {
  final PostList postList;
  PostResultSuccess(this.postList);
}
// произошла ошибка
class PostResultFailure extends PostResult {
  final String error;
  PostResultFailure(this.error);
}
// загрузка данных
class PostResultLoading extends PostResult {
  PostResultLoading();
}
Одной из наиболее неприятных проблем является несоответствие типов.Если взглянуть на JSON объект поста:
{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
То можно заметить, что userId и id являются целыми числами, а title и body строками, поэтому в конструкторе Post.fromJson(json) мы не замарачиваемся с привидением типов.Пришло время создать Repository класс. Для этого создадим новую папку data и в нем файл repository.dart:
import 'dart:convert';
// импортируем http пакет
import 'package:http/http.dart' as http;
import 'package:json_placeholder_app/models/post.dart';
// мы ещё не раз будем использовать
// константу SERVER
const String SERVER = "https://jsonplaceholder.typicode.com";
class Repository {
  // обработку ошибок мы сделаем в контроллере
  // мы возвращаем Future объект, потому что
  // fetchPhotos асинхронная функция
  // асинхронные функции не блокируют UI
  Future<PostList> fetchPosts() async {
    // сначала создаем URL, по которому
    // мы будем делать запрос
    final url = Uri.parse("$SERVER/posts");
    // делаем GET запрос
    final response = await http.get(url);
// проверяем статус ответа
if (response.statusCode == 200) {
  // если все ок то возвращаем посты
  // json.decode парсит ответ
  return PostList.fromJson(json.decode(response.body));
} else {
  // в противном случае говорим об ошибке
  throw Exception("failed request");
}
  }
}
Вы скажите: мы могли все запихнуть в контроллер, зачем создавать ещё один класс?Т.к. контроллеров может быть огромное количество и каждый из них будет обращаться к одному и тому же серверу, нам придеться дублировать логику. К тому же это не очень гибко. Вдруг нам нужно будет поменять URL адрес сервера.Реализуем PostController:
import '../data/repository.dart';
import '../models/post.dart';
import 'package:mvc_pattern/mvc_pattern.dart';
class PostController extends ControllerMVC {
  // создаем наш репозиторий
  final Repository repo = new Repository();
  // конструктор нашего контроллера
  PostController();
  // первоначальное состояние - загрузка данных
  PostResult currentState = PostResultLoading();
  void init() async {
    try {
      // получаем данные из репозитория
      final postList = await repo.fetchPosts();
      // если все ок то обновляем состояние на успешное
      setState(() => currentState = PostResultSuccess(postList));
    } catch (error) {
      // в противном случае произошла ошибка
      setState(() => currentState = PostResultFailure("Нет интернета"));
    }
  }
}
Заключительная часть: подключим наш контроллер к представлению и выведем посты:
import 'package:flutter/material.dart';
import '../controllers/post_controller.dart';
import '../models/post.dart';
import 'package:mvc_pattern/mvc_pattern.dart';
class PostListPage extends StatefulWidget {
  @override
  _PostListPageState createState() => _PostListPageState();
}
// не забываем расширяться от StateMVC
class _PostListPageState extends StateMVC {
  // ссылка на наш контроллер
  PostController _controller;
  // передаем наш контроллер StateMVC конструктору и
  // получаем на него ссылку
  _PostListPageState() : super(PostController()) {
    _controller = controller as PostController;
  }
  // после инициализации состояния
  // мы запрашивает данные у сервера
  @override
  void initState() {
    super.initState();
    _controller.init();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Post List Page"),
      ),
      body: _buildContent()
    );
  }
  Widget _buildContent() {
    // первым делом получаем текущее состояние
    final state = _controller.currentState;
    if (state is PostResultLoading) {
      // загрузка
      return Center(
        child: CircularProgressIndicator(),
      );
    } else if (state is PostResultFailure) {
      // ошибка
      return Center(
        child: Text(
          state.error,
          textAlign: TextAlign.center,
          style: Theme.of(context).textTheme.headline4.copyWith(color: Colors.red)
        ),
      );
    } else {
      // отображаем список постов
      final posts = (state as PostResultSuccess).postList.posts;
      return Padding(
        padding: EdgeInsets.all(10),
        // ListView.builder создает элемент списка
        // только когда он видим на экране
        child: ListView.builder(
          itemCount: posts.length,
          itemBuilder: (context, index) {
            return _buildPostItem(posts[index]);
          },
        ),
      );
    }
  }
  // элемент списка
  Widget _buildPostItem(Post post) {
    return Container(
        decoration: BoxDecoration(
            borderRadius: BorderRadius.all(Radius.circular(15)),
            border: Border.all(color: Colors.grey.withOpacity(0.5), width: 0.3)
        ),
        margin: EdgeInsets.only(bottom: 10),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Container(
              decoration: BoxDecoration(
                borderRadius: BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15)),
                color: Theme.of(context).primaryColor,
              ),
              padding: EdgeInsets.all(10),
              child: Text(
                post.title,
                textAlign: TextAlign.left,
                style: Theme.of(context).textTheme.headline5.copyWith(color: Colors.white),),
            ),
            Container(
              child: Text(
                post.body,
                style: Theme.of(context).textTheme.bodyText2,
              ),
              padding: EdgeInsets.all(10),
            ),
          ],
        )
    );
  }
}
Не пугайтесь если слишком много кода. Все сразу освоить невозможно, поэтому не спешите)ЗапускПопробуем запустить:
Вуаля! Теперь отключим интернет:
Все работает! Небольшая заметкаОдним из важных принципов программирования является стремление к минимизации кода и его упрощению.Файл post_list_page.dart содержит всего 110 строк кода, это не проблема. Но если бы он был в 10 или даже в 20 раз больше!Какой ужас был бы на глазах у того, кто взглянул бы на него. Лучшей практикой считается выносить повторяющие фрагменты кода в отдельные файлы.Давайте попробуем вынести функцию Widget _buildItem(post) в другой файл. Для этого создадим для каждой группы страниц свою папку:
Затем в папке post создадим новый файл post_list_item.dart:
import 'package:flutter/material.dart';
import '../../models/post.dart';
// элемент списка
class PostListItem extends StatelessWidget {
  final Post post;
  // элемент списка отображает один пост
  PostListItem(this.post);
  Widget build(BuildContext context) {
    return Container(
        decoration: BoxDecoration(
            borderRadius: BorderRadius.all(Radius.circular(15)),
            border: Border.all(color: Colors.grey.withOpacity(0.5), width: 0.3)
        ),
        margin: EdgeInsets.only(bottom: 10),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Container(
              decoration: BoxDecoration(
                borderRadius: BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15)),
                color: Theme.of(context).primaryColor,
              ),
              padding: EdgeInsets.all(10),
              child: Text(
                post.title,
                textAlign: TextAlign.left,
                style: Theme.of(context).textTheme.headline5.copyWith(color: Colors.white),),
            ),
            Container(
              child: Text(
                post.body,
                style: Theme.of(context).textTheme.bodyText2,
              ),
              padding: EdgeInsets.all(10),
            ),
          ],
        )
    );
  }
}
Не забудьте удалить ненужный код из post_list_page.dart:
import 'package:flutter/material.dart';
import '../../controllers/post_controller.dart';
import '../../models/post.dart';
import 'post_list_item.dart';
import 'package:mvc_pattern/mvc_pattern.dart';
class PostListPage extends StatefulWidget {
  @override
  _PostListPageState createState() => _PostListPageState();
}
// не забываем расширяться от StateMVC
class _PostListPageState extends StateMVC {
  // ссылка на наш контроллер
  PostController _controller;
  // передаем наш контроллер StateMVC конструктору и
  // получаем на него ссылку
  _PostListPageState() : super(PostController()) {
    _controller = controller as PostController;
  }
  // после инициализации состояние
  // мы запрашивает данные у сервера
  @override
  void initState() {
    super.initState();
    _controller.init();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Post List Page"),
      ),
      body: _buildContent()
    );
  }
  Widget _buildContent() {
    // первым делом получаем текущее состояние
    final state = _controller.currentState;
    if (state is PostResultLoading) {
      // загрузка
      return Center(
        child: CircularProgressIndicator(),
      );
    } else if (state is PostResultFailure) {
      // ошибка
      return Center(
        child: Text(
          state.error,
          textAlign: TextAlign.center,
          style: Theme.of(context).textTheme.headline4.copyWith(color: Colors.red)
        ),
      );
    } else {
      // отображаем список постов
      final posts = (state as PostResultSuccess).postList.posts;
      return Padding(
        padding: EdgeInsets.all(10),
        // ListView.builder создает элемент списка
        // только когда он видим на экране
        child: ListView.builder(
          itemCount: posts.length,
          itemBuilder: (context, index) {
            // мы вынесли элемент списка в
            // отдельный виджет
            return PostListItem(posts[index]);
          },
        ),
      );
    }
  }
}
ЗаключениеВ последующих частях мы ещё не раз будем сталкиваться с созданием сетевых запросов.Я постарался кратко рассказать и показать на наглядном примере работу с сетью.Надеюсь моя статья принесла вам пользу)Ссылка на GithubВсем хорошего кода!
===========
 Источник:
habr.com
===========
Похожие новости:
- [Разработка мобильных приложений, Разработка под Android, Gradle] Проекты в Gradle 7: как не зависеть от зависимостей
 
- [Разработка под Android, Dart, Flutter] Основы Flutter для начинающих (Часть IV)
 
- Релиз http-сервера Apache 2.4.48
 
- [Разработка под Android] Всё о PendingIntents (перевод)
 
- [Разработка под Android, Kotlin] Proto DataStore + AndroidX Preferences на Kotlin
 
- [Разработка под Android] Все новинки Android 12. Обзор для разработчиков
 
- [Разработка под Android, Dart, Flutter] Основы Flutter для начинающих (Часть II)
 
- [Java, Разработка под Android] Инициализация Rx цепочки
 
- [Разработка веб-сайтов, Python] Первые шаги в aiohttp
 
- [Информационная безопасность, Разработка под Android, Геоинформационные сервисы, Смартфоны] Google собирает данные геолокации со смартфонов, даже если запретить отслеживание
 
Теги для поиска: #_razrabotka_pod_android (Разработка под Android), #_dart, #_flutter, #_dart, #_flutter, #_http, #_clientserver, #_mvc, #_razrabotka_pod_android (
Разработка под Android
), #_dart, #_flutter
                                        
                                        
                                        
                                     
                                    
                                    
                                                                    
                                                                                             
                         
                        
                            
                                                                    
                                                             
                         
                    
                    
                
                
            
        
    
    
    
    
    
            
    
            
    
        
    
    
        
                        Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
    
    
        
        Текущее время: 04-Ноя 14:19
Часовой пояс: UTC + 5 
            
    
                
| Автор | Сообщение | 
|---|---|
| 
                                
                                
                                                                                                            news_bot ®
                                                                        
                                                                                                                                                 
                                                                            
                                                                                                                
                                            Стаж: 7 лет 8 месяцев                                          | 
                            |
| 
                                 Наконец-то мы добрались до одной из самых важных тем, без которой идти дальше нет смысла.План довольно простой: нам предстоит познакомиться с клиент-серверной архитектурой и реализовать получение списка постов.В конце мы правильно организуем файлы наших страниц и вынесем элемент списка в отдельный файл.Полетели!Наш план 
 
 [ 
{ "userId": 1, "id": 1, "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" }, { "userId": 1, "id": 2, "title": "qui est esse", "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla" }, ... ] { 
"total_items" : 1 "result" : [ { "id" : 1, "name" : "Twillight Sparkle", "pony_type" : "alicorn", "friends" : [ "Starlight Glimmer", "Applejack", "Rarity", "Spike" ] } ] } 
 # блок зависимостей 
dependencies: flutter: sdk: flutter # подключение необходимых pub-пакетов # используется для произвольного размещения # компонентов в виде сетки flutter_staggered_grid_view: ^0.4.0 # мы будем использовать MVC паттерн mvc_pattern: ^7.0.0 # http предоставляет удобный интерфейс для создания # запросов и обработки ошибок http: ^0.13.3 // сначала создаем объект самого поста 
class Post { // все поля являются private // это сделано для инкапсуляции данных final int _userId; final int _id; final String _title; final String _body; // создаем getters для наших полей // дабы только мы могли читать их int get userId => _userId; int get id => _id; String get title => _title; String get body => _body; // Dart позволяет создавать конструкторы с разными именами // В данном случае Post.fromJson(json) - это конструктор // здесь мы принимаем JSON объект поста и извлекаем его поля // обратите внимание, что dynamic переменная // может иметь разные типы: String, int, double и т.д. Post.fromJson(Map<String, dynamic> json) : this._userId = json["userId"], this._id = json["id"], this._title = json["title"], this._body = json["body"]; } // PostList являются оберткой для массива постов class PostList { final List<Post> posts = []; PostList.fromJson(List<dynamic> jsonItems) { for (var jsonItem in jsonItems) { posts.add(Post.fromJson(jsonItem)); } } } // наше представление будет получать объекты // этого класса и определять конкретный его // подтип abstract class PostResult {} // указывает на успешный запрос class PostResultSuccess extends PostResult { final PostList postList; PostResultSuccess(this.postList); } // произошла ошибка class PostResultFailure extends PostResult { final String error; PostResultFailure(this.error); } // загрузка данных class PostResultLoading extends PostResult { PostResultLoading(); } { 
"userId": 1, "id": 1, "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" } import 'dart:convert'; 
// импортируем http пакет import 'package:http/http.dart' as http; import 'package:json_placeholder_app/models/post.dart'; // мы ещё не раз будем использовать // константу SERVER const String SERVER = "https://jsonplaceholder.typicode.com"; class Repository { // обработку ошибок мы сделаем в контроллере // мы возвращаем Future объект, потому что // fetchPhotos асинхронная функция // асинхронные функции не блокируют UI Future<PostList> fetchPosts() async { // сначала создаем URL, по которому // мы будем делать запрос final url = Uri.parse("$SERVER/posts"); // делаем GET запрос final response = await http.get(url); // проверяем статус ответа if (response.statusCode == 200) { // если все ок то возвращаем посты // json.decode парсит ответ return PostList.fromJson(json.decode(response.body)); } else { // в противном случае говорим об ошибке throw Exception("failed request"); } } } import '../data/repository.dart'; 
import '../models/post.dart'; import 'package:mvc_pattern/mvc_pattern.dart'; class PostController extends ControllerMVC { // создаем наш репозиторий final Repository repo = new Repository(); // конструктор нашего контроллера PostController(); // первоначальное состояние - загрузка данных PostResult currentState = PostResultLoading(); void init() async { try { // получаем данные из репозитория final postList = await repo.fetchPosts(); // если все ок то обновляем состояние на успешное setState(() => currentState = PostResultSuccess(postList)); } catch (error) { // в противном случае произошла ошибка setState(() => currentState = PostResultFailure("Нет интернета")); } } } import 'package:flutter/material.dart'; 
import '../controllers/post_controller.dart'; import '../models/post.dart'; import 'package:mvc_pattern/mvc_pattern.dart'; class PostListPage extends StatefulWidget { @override _PostListPageState createState() => _PostListPageState(); } // не забываем расширяться от StateMVC class _PostListPageState extends StateMVC { // ссылка на наш контроллер PostController _controller; // передаем наш контроллер StateMVC конструктору и // получаем на него ссылку _PostListPageState() : super(PostController()) { _controller = controller as PostController; } // после инициализации состояния // мы запрашивает данные у сервера @override void initState() { super.initState(); _controller.init(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Post List Page"), ), body: _buildContent() ); } Widget _buildContent() { // первым делом получаем текущее состояние final state = _controller.currentState; if (state is PostResultLoading) { // загрузка return Center( child: CircularProgressIndicator(), ); } else if (state is PostResultFailure) { // ошибка return Center( child: Text( state.error, textAlign: TextAlign.center, style: Theme.of(context).textTheme.headline4.copyWith(color: Colors.red) ), ); } else { // отображаем список постов final posts = (state as PostResultSuccess).postList.posts; return Padding( padding: EdgeInsets.all(10), // ListView.builder создает элемент списка // только когда он видим на экране child: ListView.builder( itemCount: posts.length, itemBuilder: (context, index) { return _buildPostItem(posts[index]); }, ), ); } } // элемент списка Widget _buildPostItem(Post post) { return Container( decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(15)), border: Border.all(color: Colors.grey.withOpacity(0.5), width: 0.3) ), margin: EdgeInsets.only(bottom: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Container( decoration: BoxDecoration( borderRadius: BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15)), color: Theme.of(context).primaryColor, ), padding: EdgeInsets.all(10), child: Text( post.title, textAlign: TextAlign.left, style: Theme.of(context).textTheme.headline5.copyWith(color: Colors.white),), ), Container( child: Text( post.body, style: Theme.of(context).textTheme.bodyText2, ), padding: EdgeInsets.all(10), ), ], ) ); } } ![]() Вуаля! Теперь отключим интернет: ![]() Все работает! Небольшая заметкаОдним из важных принципов программирования является стремление к минимизации кода и его упрощению.Файл post_list_page.dart содержит всего 110 строк кода, это не проблема. Но если бы он был в 10 или даже в 20 раз больше!Какой ужас был бы на глазах у того, кто взглянул бы на него. Лучшей практикой считается выносить повторяющие фрагменты кода в отдельные файлы.Давайте попробуем вынести функцию Widget _buildItem(post) в другой файл. Для этого создадим для каждой группы страниц свою папку: ![]() Затем в папке post создадим новый файл post_list_item.dart: import 'package:flutter/material.dart'; 
import '../../models/post.dart'; // элемент списка class PostListItem extends StatelessWidget { final Post post; // элемент списка отображает один пост PostListItem(this.post); Widget build(BuildContext context) { return Container( decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(15)), border: Border.all(color: Colors.grey.withOpacity(0.5), width: 0.3) ), margin: EdgeInsets.only(bottom: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Container( decoration: BoxDecoration( borderRadius: BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15)), color: Theme.of(context).primaryColor, ), padding: EdgeInsets.all(10), child: Text( post.title, textAlign: TextAlign.left, style: Theme.of(context).textTheme.headline5.copyWith(color: Colors.white),), ), Container( child: Text( post.body, style: Theme.of(context).textTheme.bodyText2, ), padding: EdgeInsets.all(10), ), ], ) ); } } import 'package:flutter/material.dart'; 
import '../../controllers/post_controller.dart'; import '../../models/post.dart'; import 'post_list_item.dart'; import 'package:mvc_pattern/mvc_pattern.dart'; class PostListPage extends StatefulWidget { @override _PostListPageState createState() => _PostListPageState(); } // не забываем расширяться от StateMVC class _PostListPageState extends StateMVC { // ссылка на наш контроллер PostController _controller; // передаем наш контроллер StateMVC конструктору и // получаем на него ссылку _PostListPageState() : super(PostController()) { _controller = controller as PostController; } // после инициализации состояние // мы запрашивает данные у сервера @override void initState() { super.initState(); _controller.init(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Post List Page"), ), body: _buildContent() ); } Widget _buildContent() { // первым делом получаем текущее состояние final state = _controller.currentState; if (state is PostResultLoading) { // загрузка return Center( child: CircularProgressIndicator(), ); } else if (state is PostResultFailure) { // ошибка return Center( child: Text( state.error, textAlign: TextAlign.center, style: Theme.of(context).textTheme.headline4.copyWith(color: Colors.red) ), ); } else { // отображаем список постов final posts = (state as PostResultSuccess).postList.posts; return Padding( padding: EdgeInsets.all(10), // ListView.builder создает элемент списка // только когда он видим на экране child: ListView.builder( itemCount: posts.length, itemBuilder: (context, index) { // мы вынесли элемент списка в // отдельный виджет return PostListItem(posts[index]); }, ), ); } } } =========== Источник: habr.com =========== Похожие новости: 
 Разработка под Android ), #_dart, #_flutter  | 
                        |
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
    Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 04-Ноя 14:19
Часовой пояс: UTC + 5