Initstate flutter statefull как сделать
Перейти к содержимому

Initstate flutter statefull как сделать

  • автор:

Жизненный цикл приложения во Flutter

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

Жизненный цикл приложения Flutter относится к нескольким этапам, которые проходит приложение при запуске, запуске и завершении работы. Понимание жизненного цикла приложения имеет решающее значение, поскольку оно позволяет создавать код, который выполняется в определенные моменты жизненного цикла, например, при инициализации приложения или при его закрытии.

Во Flutter доступно несколько крючков жизненного цикла приложения. Эти перехватчики позволяют приложению выполнять код в определенные моменты жизненного цикла приложения, например, при инициализации приложения, когда приложение становится видимым или неактивным и когда приложение закрывается. Крючки жизненного цикла перечислены следующим образом:

  1. initState()
  2. didChangeDependencies()
  3. build()
  4. didUpdateWidget()
  5. dispose()

Вот краткий обзор каждого крючка:

initState()

Метод initState() — это перехватчик жизненного цикла, который вызывается при инициализации приложения. Этот метод является хорошим местом для инициализации любого состояния приложения, такого как загрузка данных с удаленного сервера или подключение к базе данных.

Вот пример того, как метод initState() может быть использован в приложении Flutter:

class InitExample extends StatefulWidget < @override _InitExampleState createState() =>_InitExampleState(); > class _InitExampleState extends State < String data; @override void initState() < super.initState(); fetchData().then((value) < setState(() < data = value; >); >); > @override Widget build(BuildContext context) < return Scaffold( body: data != null ? Text(data) : CircularProgressIndicator(), ); >>

Метод initState() используется в предыдущем примере для извлечения данных с сервера и сохранения их в переменной data. Затем данные отображаются на экране с помощью метода build() . Вместо этого отображается знак загрузки, если данные еще не доступны.

didChangeDependencies()

Каждый раз, когда изменяются зависимости виджета, вызывается метод didChangeDependencies() . Понимание этого метода важно, поскольку оно помогает вам обновлять состояние виджета в зависимости от изменений в его зависимостях.

Вот как вы могли бы использовать метод didChangeDependencies() в приложении Flutter:

 @override void didChangeDependencies()

Важно отметить, что хук didChangeDependencies() вызывается всякий раз, когда изменяются зависимости виджета, а не только при изменении значения унаследованного виджета. Это означает, что вы должны быть осторожны и обновлять состояние виджета только при необходимости, так как ненужное обновление состояния может привести к проблемам с производительностью.

build()

Метод build() используется для создания пользовательского интерфейса для вашего приложения.

Этот метод должен возвращать виджет, представляющий пользовательский интерфейс приложения. Он вызывается каждый раз, когда приложению необходимо обновить свой внешний вид, и отвечает за создание виджетов, составляющих пользовательский интерфейс приложения.

Вот простой пример того, как используется метод build() :

class InitExample extends StatefulWidget < @override _InitExampleState createState() =>_InitExampleState(); > class _InitExampleState extends State  < String data = "This is an example"; @override Widget build(BuildContext context) < return Scaffold( body: Text(data), ); >> 

Метод build() вызывается часто, поэтому важно убедиться, что он эффективен и выполняет только то, что необходимо для создания пользовательского интерфейса. Это помогает поддерживать бесперебойную и быструю работу приложения.

didUpdateWidget()

Метод didUpdateWidget() вызывается каждый раз, когда виджет перестраивается и его зависимости изменяются. Этот метод полезен для обновления состояния виджета на основе изменений его зависимостей.

Вот как вы могли бы использовать метод didUpdateWidget() в приложении Flutter:

@override void didUpdateWidget(MyApp oldWidget)

Метод didUpdateWidget() вызывается всякий раз, когда виджет перестраивается и его зависимости изменяются, поэтому важно убедиться, что он эффективен и делает только то, что необходимо для обновления состояния виджета.

dispose()

Метод dispose() используется для очистки любых ресурсов, удерживаемых состоянием при его закрытии.

 StreamSubscription _streamSubscription; @override void dispose()

В предыдущем примере метод dispose() используется для закрытия соединения StreamSubscription. Это высвобождает ресурсы и обеспечивает эффективную работу приложения.

Вывод

Чтобы создать плавный и бесшовный пользовательский интерфейс в вашем приложении Flutter, вы должны сначала понять жизненный цикл приложения.

Мы надеемся, вы нашли это краткое объяснение жизненного цикла приложения Flutter полезным! Если у вас есть какие-либо вопросы, не стесняйтесь обращаться к нам.

Initstate flutter statefull как сделать

Класс StatefulWidget предназначен для создания виджетов, которые хранят состояние. При этом несмотря на то, что объекты класса StatefulWidget являются неизменяемыми (immutable), их состояние является изменяемым (mutable).

Что представляет собой состояние? Состояние — это некоторая информация, которая может быть считана синхронно при создании виджета и которая в процессе жизненного цикла виджета может измениться. Состояние может храниться в виде отдельных объектов State, либо в других объектах, на которые объект State подписывается.

Объекты State создаются с помощью метода createState , который вызывается фреймворком и который связывает их с виджетом StatefulWidget.

Например, определим простейший виджет StatefulWidget:

import 'package:flutter/material.dart'; void main() < runApp(MaterialApp( home: Scaffold( body: Counter(), appBar: AppBar(title: Text("METANIT.COM")),) )); >class Counter extends StatefulWidget< Counter(< super.key>); @override _CounterState createState() => _CounterState(); > class _CounterState extends State < int value = 0; @override Widget build(BuildContext context) < return Text( "Value: $value", style: TextStyle(fontSize: 22), ); >>

Итак,здесь определен класс Counter, который наследуется от StatefulWidget.

Его конструктор содержит один параметр — key, хотя при необходимости мы можем определять какие-то другие параметры, исходя из наших потребностей.

Также класс виджета должен переопределить метод createState() , который должен возвращать состояние для этого виджета

@override _CounterState createState() => _CounterState();

В данном случае это объект класса _CounterState . (Классы или члены классов, которые предваряются знаком прочерка (_), являются приватными по отношению к классам из других библиотек.)

Сам класс состояния унаследован от класса State , который типизирован классом класса виджета.

class _CounterState extends State

В самом классе определена переменная value , которая условно представляет данные, которые хранит класс состояния.

int value = 0;

Также нам надо переопределить метод build() , который возвращает виджет:

Widget build(BuildContext context)

Здесь мы просто возвращаем виджет Text , который выводит значение переменной value . Но естественно возвращаемая иерархия виджетов может быть более сложной.

Затем виджет Counter встравивается в приложение в элемент Scaffold:

void main() < runApp(MaterialApp( home: Scaffold( body: Counter(),

В итоге при запуске приложения мы увидим текст со значением переменной value

StatefulWidget в Flutter

Но ключевая идея StatefulWidget состоит в том, что мы можем менять его состояние. Поэтому добавим изменение переменной value по нажатию на кнопку:

import 'package:flutter/material.dart'; void main() < runApp(MaterialApp( home: Scaffold( body: Counter(), appBar: AppBar(title: Text("METANIT.COM")),) )); >class Counter extends StatefulWidget< Counter(< super.key>); @override _CounterState createState() => _CounterState(); > class _CounterState extends State< int value = 0; @override Widget build(BuildContext context) < return ElevatedButton( child: Text("Value: $value", style: TextStyle(fontSize: 22)), onPressed:()< setState(() < value++; >);> ); > >

Теперь для простоты значение переменной value выводится в качестве текста на кнопку. При нажатии на кнопку будет вызываться функция, которая увеличит значение value на единицу. Чтобы указать, что состояние изменилось, виджет вызывает метод State.setState() . В этот метод передается функция, которая не принимает параметров и ничего не возвращает. И именно в ней мы можем изменить значение value.

В итоге по нажатию на кнопку увеличится значение переменной value:

StatefulWidget и изменение состояния State с помощью метода State.setState в Flutter

Передача данных в State

При необходимости в объект State можно передавать данные извне. В этом случае передача осуществляется через StatefulWidget. Например, изменим выше приведенный пример таким образом, чтобы State принимал начальные данные извне:

import 'package:flutter/material.dart'; void main() < runApp(MaterialApp( home: Scaffold( body: Column(children:[ Counter(value: 4, increment: 2), Counter(value:-1, increment: 1) ]), appBar: AppBar(title: Text("METANIT.COM")),) )); >class Counter extends StatefulWidget< int value = 0; int increment = 1; Counter(< super.key, required this.value, required this.increment>); @override _CounterState createState() => _CounterState(this.value, this.increment); > class _CounterState extends State< int value = 0; int increment = 1; _CounterState(this.value, this.increment); @override Widget build(BuildContext context) < return ElevatedButton( child: Text("Value: $value", style: TextStyle(fontSize: 22)), onPressed:()< setState(() < value = value + increment; >);> ); > >

В данном случае в State добавлена новая переменная increment , на которую увеличивается значение переменной value. Для получения извне значений для этих переменных определен конструктор _CounterState(this.value, this.increment)

При создании объекта State виджет Counter передает в конструктор соответствующие данные:

_CounterState createState() => _CounterState(this.value, this.increment);

При этом эти значения виджет Counter сам принимает извне с помощью своего конструктора:

Counter(< super.key, required this.value, required this.increment>);

В итоге при применении виджета мы можем передавать в него различные данные:

Column(children:[ Counter(value: 4, increment: 2), Counter(value:-1, increment: 1) ])

Передача данных в StatefulWidget и State и метод State.setState в Flutter

Получение виджета

С помощью свойства widget внутри State можно обращаться к виджету StatefulWidget, к которому привязан объект State:

import 'package:flutter/material.dart'; void main() < runApp(MaterialApp( home: Scaffold( body: Column(children:[ Counter(increment: 2), Counter(increment: 1), ]), appBar: AppBar(title: Text("METANIT.COM")),) )); >class Counter extends StatefulWidget< int increment = 1; Counter(< super.key, required this.increment>); @override _CounterState createState() => _CounterState(); > class _CounterState extends State< int value = 0; @override Widget build(BuildContext context) < return ElevatedButton( child: Text("Value: $value", style: TextStyle(fontSize: 22)), onPressed:()< setState(() < value = value + widget.increment; >);> ); > >

В данном случае переменная increment определена только внутри класса Counter, и чтобы к ней обратиться, применяется выражение widget.increment , где widget - это виджет Counter. Подобным образом можно обращаться к другим полям и методам из виджета при их наличии.

Вынос логики изменения состояния в метод

В примерах выше вся логика изменения состояния сосредоточена в функции из onPressed . Но все эти действия также можно вынести в отдельный метод:

import 'package:flutter/material.dart'; void main() < runApp(MaterialApp( home: Scaffold( body: Column(children:[ Counter(increment: 2), Counter(increment: 1), ]), appBar: AppBar(title: Text("METANIT.COM")),) )); >class Counter extends StatefulWidget< int increment = 1; Counter(< super.key, required this.increment>); @override _CounterState createState() => _CounterState(); > class _CounterState extends State< int value = 0; increaseValue()< setState(() < value = value + widget.increment; >); > @override Widget build(BuildContext context) < return ElevatedButton( child: Text("Value: $value", style: TextStyle(fontSize: 22)), onPressed:()< increaseValue();>); > >

Если метод, в который вынесены все действия, ничего не возвращает и не принимает никаких параметров, то есть соответствует определению onPressed (как в данном случае), то можно напрямую присвоить функцию параметру onPressed:

ElevatedButton( child: Text("Value: $value", style: TextStyle(fontSize: 22)), onPressed: increaseValue );

What is the Use of initState in Flutter?

The word initState is made up of two words: the first word being init, which means initialize, and the other word being state. It is a method of the State class that is used to initialize the state of a Stateful Widget. When the stateful widget is inserted into the widget tree for the first time, initstate is called. It is also used to set up necessary data before the widget is rendered on the screen. Overall, it plays a very important role in the lifecycle of a StatefulWidget for it provides a convenient entry point for preparing the widget's state.

What is the use of initState in Flutter?

The main use of the initState is to perform initialization tasks which are important for the widget's functionality. It allows us to execute the code which must be run before the build() method is called. State variables hold data that can change over time. initstate helps us to set up their initial values so that the widget starts with the desired initial state. It is also used to execute setup tasks like setting up listeners, initializing controllers or animations, connecting to external services, etc. By performing these setup tasks in initState, we ensure that all the necessary components are ready before the widget is displayed. If we have to fetch some data from APIs, databases, or other sources, using initstate ensures that we have the data before the widget is rendered. Streams are used to handle asynchronous data. Using initstate helps us update the widgets and trigger rebuilds whenever new data is received

Uses of initstate in Flutter

a) To initialize data that depends on the specific BuildContext:

initState allows us to initialize data that depends on a specific BuildContext, which in turn provides information about the widget's location in the widget tree. The most common use case of this phenomenon is to fetch data from APIs, databases, or other sources. For example, If you have a StatefulWidget that needs to fetch user data from an API then using initState ensures that the data will be available when the build() method is called. In the example shown below: The fetchUserData() function takes the BuildContext as a parameter and is called to retrieve the data. The fetched data is then stored in the userData variable, which is used to display the user data in the build() method.

b) To initialize data that needs to execute before build():

There are times when we need to execute some data before the build() method is actually called. We need to set up variables, initialize state, etc. as they are essential for the widget's functionality. In the example given below: We have called the initializeData() method inside the initState() method. It ensures that the counter value is set before displaying it on the screen.

c) Subscribe to Streams:

Streams are used in Flutter to handle asynchronous data. By subscribing to a Stream in initstate, we can listen to the data events emitted by the stream and update the widget accordingly. In the example given below, we use the _subscribeToStream() method which sets up a stream using the Stream.periodic() constructor. The count is incremented every second and then the setstate is called in order to rebuild the widget. To prevent the memory leaks or unnecessary usage of resources, we call the subscription?.cancel() inside the dispose() method;

Limitations and Best Practices

Having discussed the use cases of initstate flutter, it is equally important to know about its limitations and the best practices we need to keep in mind while using it. We are going to discuss some of its limitations and best practices.

Общие принципы

Flutter — реактивный фреймворк, и для разработчика, специализирующегося на нативной разработке, его философия может быть непривычна. Поэтому начнём с небольшого обзора.

Пользовательский интерфейс на Flutter, как и в большинстве современных фреймворков, состоит из дерева компонентов (виджетов). При изменении какого-либо компонента, происходит перерендеринг этого и всех его дочерних компонентов (с внутренними оптимизациями, о которых ниже). При глобальном изменении отображения (например, повороте экрана), перерисовывается всё дерево виджетов.

Этот подход может показаться неэффективным, но на самом деле он передаёт программисту контроль за скоростью работы. Если производить обновление интерфейса на самом верхнем уровне без необходимости — всё будет работать медленно, но при правильной компоновке виджетов, приложения на Flutter могут быть очень быстрыми.

Во Flutter существует два типа виджетов — Stateless и Stateful. Первые (аналог Pure Components в React) не имеют состояния и полностью описываются своими параметрами. Если не меняются условия отображения (скажем, размер области, в которой должен показываться виджет) и его параметры, система переиспользует ранее созданное визуальное представление виджета, поэтому использование Stateless виджетов хорошо сказывается на производительности. При этом всё равно при каждой перерисовке виджета формально создаётся новый объект и запускается конструктор.

Stateful виджеты сохраняют некоторое состояние между рендерингами. Для этого они описываются двумя классами. Первый из классов, собственно виджет, описывает объекты, которые создаются при каждой отрисовке. Второй класс, описывает состояние виджета и его объекты передаются в создаваемые объекты виджета. Изменение состояния Stateful виджетов является основным источником перерисовки интерфейсов. Для этого нужно изменить его свойства внутри вызова метода SetState. Таким образом, в отличие от многих других фреймворков, во Flutter нет неявного отслеживания состояния — любое изменение свойств виджета вне метода SetState не приводит к перерисовке интерфейса.

Теперь, после описания основ, можно начать с простого приложения, использующего Stateless и Stateful виджеты:

Базовое приложение

import 'dart:math'; import 'package:flutter/material.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget < @override Widget build(BuildContext context) < return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( appBar: AppBar( title: Text('Sample app'), ), body: new MyHomePage(), ), ); >> class MyHomePage extends StatefulWidget < @override _MyHomePageState createState() =>_MyHomePageState(); > class _MyHomePageState extends State < Random rand = Random(); @override Widget build(BuildContext context) < return new ListView.builder(itemBuilder: (BuildContext context, int index) < return Text('Random number $',); >); > >

Результат

Если нужны более живучие состояния

Идём дальше. Состояние Stateful виджетов сохраняется между перерисовками интерфейсов, но только до тех пор, пока виджет нужен, т.е. реально находится на экране. Проведём простой эксперимент — разместим наш список на вкладке:

Приложение с вкладками

class _MyHomePageState extends State with SingleTickerProviderStateMixin < Random rand = Random(); TabController _tabController; final ListmyTabs = [ new Tab(text: 'FIRST'), new Tab(text: 'SECOND'), ]; @override void initState() < super.initState(); _tabController = new TabController(vsync: this, length: myTabs.length); >@override Widget build(BuildContext context) < return Scaffold( appBar: AppBar( title: Text('Sample app'), ), body: new TabBarView( controller: _tabController, children: [ new ListView.builder(itemBuilder: (BuildContext context, int index) < return Text('Random number $',); >), Text('Second tab'), ],), bottomNavigationBar: new TabBar( controller: _tabController, tabs: myTabs, labelColor: Colors.blue, ), ); > >

Результат

При запуске можно увидеть, что при переключении между вкладками, состояние удаляется (вызывается метод dispose()), при возврате создаётся снова (метод initState()). Это разумно, так как хранение состояния неотображаемых виджетов будет отнимать ресурсы системы. В том случае, когда состояние виджета должно переживать его полное скрытие, возможны несколько подходов:

Во-первых, можно использовать отдельные объекты (ViewModel) для хранения состояния. Dart на уровне языка поддерживает фабричные конструкторы, которые можно использовать для создания фабрик и синглтонов, хранящих необходимые данные.

Мне больше нравится этот подход, т.к. он позволяет изолировать бизнес-логику от пользовательского интерфейса. Это особенно актуально в связи с тем, что Flutter Release Preview 2 добавил возможность создавать pixel-perfect интерфейсы для iOS, но делать это нужно, разумеется, на соответствующих виджетах.

Во-вторых, можно использовать знакомый программистам React подход поднятия состояния, когда данные хранятся в компонентах, расположенных выше по дереву. Поскольку Flutter перерисовывает интерфейс только при вызове метода setState(), эти данные можно менять и использовать без рендеринга. Такой подход несколько более сложен и повышает связность виджетов в структуре, но позволяет точечно задавать уровень хранения данных.

Наконец существуют библиотеки хранения состояния, например flutter_redux.

Для простоты используем первый подход. Сделаем отдельный класс ListData, синглтон, хранящий значения для нашего списка. При отображении будем использовать этот класс.

Приложение с вкладками и восстановлением данных

class _MyHomePageState extends State with SingleTickerProviderStateMixin < TabController _tabController; final ListmyTabs = [ new Tab(text: 'FIRST'), new Tab(text: 'SECOND'), ]; @override void initState() < super.initState(); _tabController = new TabController(vsync: this, length: myTabs.length); >@override Widget build(BuildContext context) < return Scaffold( appBar: AppBar( title: Text('Sample app'), ), body: new TabBarView( controller: _tabController, children: [ new ListView.builder(itemBuilder: ListData().build), Text('Second tab'), ],), bottomNavigationBar: new TabBar( controller: _tabController, tabs: myTabs, labelColor: Colors.blue, ), ); >> class ListData < static ListData _instance = ListData._internal(); ListData._internal(); factory ListData() < return _instance; >Random _rand = Random(); Map _values = new Map(); Widget build (BuildContext context, int index) < if (!_values.containsKey(index)) < _values[index] = _rand.nextInt(100); >return Text('Random number $',); > >

Результат

Сохранение позиции скролла

Если скрутить список из предыдущего примера вниз, потом перейти между вкладками, нетрудно заметить, что позиция прокрутки не сохраняется. Это логично, так как в нашем классе ListData она не хранится, а собственное состояние виджета не переживает переключение между табами. Реализуем хранение состояния прокрутки вручную, но для интереса сложим её не в отдельный класс и не в ListData, а в состояние более высокого уровня, чтобы показать, как с этим работать.

Обратите внимание на виджеты ScrollController и NotificationListener (а также ранее использованный DefaultTabController). Концепция виджетов, не имеющих своего отображения должна быть знакома разработчикам, работающим с React/Redux — в этой связке активно используются компоненты-контейнеры. Во Flutter виджеты без отображения обычно используются для добавления функциональности к дочерним виджетам. Это позволяет оставить сами визуальные виджеты легковесными и не обрабатывать системные события там, где они не нужны.

  1. Добавляем к списку ScrollController, чтобы работать с положением прокрутки.
  2. Добавляем к списку NotificationListener, чтобы передавать состояние прокрутки.
  3. Сохраняем положение прокрутки в _MyHomePageState (которое находится по уровню выше табов) и связываем его с прокруткой списка.

Приложение с сохранением положения прокрутки

class _MyHomePageState extends State with SingleTickerProviderStateMixin < double listViewOffset=0.0; TabController _tabController; final ListmyTabs = [ new Tab(text: 'FIRST'), new Tab(text: 'SECOND'), ]; @override void initState() < super.initState(); _tabController = new TabController(vsync: this, length: myTabs.length); >@override Widget build(BuildContext context) < return Scaffold( appBar: AppBar( title: Text('Sample app'), ), body: new TabBarView( controller: _tabController, children: [new ListTab( getOffsetMethod: () =>listViewOffset, setOffsetMethod: (offset) => this.listViewOffset = offset, ), Text('Second tab'), ],), bottomNavigationBar: new TabBar( controller: _tabController, tabs: myTabs, labelColor: Colors.blue, ), ); > > class ListTab extends StatefulWidget < ListTab() : super(key: key); final GetOffsetMethod getOffsetMethod; final SetOffsetMethod setOffsetMethod; @override _ListTabState createState() => _ListTabState(); > class _ListTabState extends State  < ScrollController scrollController; @override void initState() < super.initState(); //Init scrolling to preserve it scrollController = new ScrollController( initialScrollOffset: widget.getOffsetMethod() ); >@override Widget build(BuildContext context) < return NotificationListener( child: new ListView.builder( controller: scrollController, itemBuilder: ListData().build, ), onNotification: (notification) < if (notification is ScrollNotification) < widget.setOffsetMethod(notification.metrics.pixels); >>, ); > >

Результат

Переживаем выключение приложения

Сохранение информации на время работы приложения — это хорошо, но часто хочется сохранять её и между сеансами, особенно учитывая привычку операционных систем закрывать фоновые приложения при нехватке памяти. Основные варианты постоянного хранения данных во Flutter это:

  1. Shared preferences ( https://pub.dartlang.org/packages/shared_preferences ) является обёрткой вокруг NSUserDefaults (на iOS) и SharedPreferences (на Android) и позволяет хранить небольшое количество пар ключ-значение. Отлично подходит для хранения настроек.
  2. sqflite ( https://pub.dartlang.org/packages/sqflite ) — плагин для работы с SQLite (с некоторомы ограничениями). Поддерживает как низкоуровневые запросы, так и хелперы. Кроме того, по аналогии с Room позволяет работать с версиями схемы БД и задавать код для обновления схемы при обновлении приложения.
  3. Cloud Firestore ( https://pub.dartlang.org/packages/cloud_firestore ) — часть семейства официальных плагинов для работы с FireBase.

Для демонстрации сделаем сохранение состояния прокрутки в Shared preferences. Для этого добавим восстановление позиции скролла при инициализации состояния _MyHomePageState и сохранение при прокрутке.

Здесь нужно немного остановиться на асинхронной модели Flutter/Dart, поскольку все внешние службы работают на асинхронных вызовах. Принцип работы этой модели сходен с node.js — есть один основной поток выполнения (thread), который прерывается на асинхронные вызовы. На каждом следующем прерывании (а UI делает их постоянно) обрабатываются результаты завершённых асинхронных операций.При этом есть возможность запускать тяжеловесные вычисления в фоновых threads (через функцию compute).

Итак, запись и чтение в SharedPreferences делаются асинхронно (хотя библиотека позволяет синхронное чтение из кэша). Для начала разберёмся с чтением. Стандартный подход к асинхронному получению данных выглядит так — запустить асинхронный процесс, по его завершению выполнить SetState, записав полученные значения. В результате пользовательский интерфейс будет обновлён с использованием полученных данных. Однако в данном случае мы работаем не с данными, а с положением прокрутки. Нам не нужно обновлять интерфейс, нужно только вызвать метод jumpTo у ScrollController. Проблема в том, что результат обработки асинхронного запроса может вернуться в любой момент и совсем не обязательно будет что и куда прокручивать. Чтобы гарантированно выполнить операцию на полностью инициализированном интерфейсе, нам нужно … всё-таки выполнить прокрутку внутри setState.

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

Установка состояния

 @override void initState() < super.initState(); //Init scrolling to preserve it scrollController = new ScrollController( initialScrollOffset: widget.getOffsetMethod() ); _restoreState().then((double value) =>scrollController.jumpTo(value)); > Future _restoreState() async < SharedPreferences prefs = await SharedPreferences.getInstance(); return prefs.getDouble('listViewOffset'); >void setScroll(double value) < setState(() < scrollController.jumpTo(value); >); >

С записью всё интереснее. Дело в том, что в процессе прокрутки, сообщающие об этом события приходят постоянно. Запуск асинхронной записи при каждом изменении значения может привести к ошибкам приложения. Нам нужно обрабатывать только последнее событие из цепочки. В терминах реактивного программирования это называется debounce и его мы и будем использовать. Dart поддерживает основные возможности реактивного программирования через потоки данных (stream), соответственно нам нужно будет создать поток из обновлений позиции прокрутки и подписаться на него, преобразуя его с помощью Debounce. Для преобразования нам потребуется библиотека stream_transform. В качестве альтернативного подхода, можно использовать RxDart и работать в терминах ReactiveX.

Получается такой код:

Запись состояния

 StreamSubscription _stream; StreamController _controller = new StreamController.broadcast(); @override void initState() < super.initState(); _tabController = new TabController(vsync: this, length: myTabs.length); _stream = _controller.stream.transform(debounce(new Duration(milliseconds: 500))).listen(_saveState); >void _saveState(double value) async

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *