どのようなときにflutterでsetstat ()とは何を使用するか?
setState
フラッターでこれを達成する方法の一つです.概要
What is a State Object in flutter
Going one step ahead
技術用語
These are some common terms used in flutter. The concepts apply to other frameworks also, although each framework has its own technical term for them.
Widget: Any UI component on the screen is called a Widget in flutter. A Widget can have its own Widgets, like a tree structure.
StatefulWidget: A Widget that can change dynamically. Generally used when we want to modify something on the screen's UI.
フラッターの状態オブジェクトは何ですか?
setState
is called inside a State class. Let's understand this in detail.State is simply the information of a StatefulWidget. Every StatefulWidget にはstateオブジェクトがあります.この状態オブジェクトは、statefulwidget内で定義する変数と関数の追跡を行います.
State Object is actually managed by corresponding Element Object of the Widget, but for this blog, we will only focus on the Widget part. If you don't know what Element Object is, I'll encourage you to read about it. It's not required to know for this blog though.
class MyWidget extends StatefulWidget { // immutable Widget
@override
_MyWidgetState createState() => _MyWidgetState();
// creating State Object of MyWidget
}
class _MyWidgetState extends State<MyWidget> { // State Object
@override
Widget build(BuildContext context) {
return Container();
}
}
StateFileWidget自体は不変です(変更できません)、我々は使用しますState Object UIを変更するには私たちは、この状態オブジェクトに、私たちのスクリーンのUIを
setState()
.関数定義
void setState(VoidCallback fn) {
...
}
このsetState()
関数をパラメータとして受け取ります.VoidCallback
ちょっとおしゃれなやり方です.void Function()
typedef VoidCallback = void Function();
例
我々は、単純なカウンタアプリを作成します(私はそれは非常に一般的ですが、私と一緒に負担)知っている.
counter
で初期化0
. counter
画面の内側にText
ウィジェット.counter
's価値1
. counter
. 一歩上に書かれた手順を見ましょう.
ウィジェットが作成されると、カウンタの値は0です
int counter = 0;
画面上のカウンタの値を表示する
Widget build(BuildContext context){
return
...
Text(`counter value: $counter`)
...
}
ボタンをクリックすると、カウンタの値がインクリメントされます
onTap: () {
// passing an anonymous function to setState
// that increments counter's value by 1
counter++;
},
UIを更新
onTap: () {
// passing an anonymous function to setState
// that increments counter's value by 1
// and update the UI
setState(() {
counter++;
});
},
楽しい事実
We can update our variables first and then call the setState()
function, since setState
just informs the underlying framework that
"update this Widget's UI in next frame" (
marks it dirty
).
The underlying framework will use the last value that is defined before calling the setState
function.
これは上と同じです
onTap: () {
counter++;
setState(() {});
},
コード例
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget { // widget class
const MyWidget({Key? key}) : super(key: key);
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> { // state class
int counter = 0; // initializing counter
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// displaing the counter
Text(
'counter value: $counter',
textAlign: TextAlign.center,
),
TextButton(
onPressed: () {
//incrementing counter
setState(() {
counter++;
});
},
child: const Text('Tap here to increment counter'),
)
],
),
);
}
}
setstat ()を使用する場合は?
When we want to change the UI of the screen.
We don't need to call setState
every time we change a variable. We call setState
only when we want the change in a variable to reflect on the UI of the screen.
For instance, say you have a form containing a text field and a button to submit it.
import 'package:flutter/material.dart';
class MyForm extends StatefulWidget {
const MyForm({Key? key}) : super(key: key);
@override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
@override
Widget build(BuildContext context) {
return Column(
children: [
// text field
Form(
child: TextFormField(),
),
// submit button
OutlinedButton(
onPressed: () {},
child: const Text('Submit'),
),
],
);
}
}
User types in the text field and clicks on submit button. Then we display that text field's text below the submit button.
import 'package:flutter/material.dart';
class MyForm extends StatefulWidget {
const MyForm({Key? key}) : super(key: key);
@override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
// we'll use this to save the text user types
String userText = "";
// this will keep track of submit button's tapped action/event
// and display the userText below submit button
bool hasSubmitted = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
Form(
child: TextFormField(
// triggered when we save the form
onSaved: (value) {
// store the text-field's value into
// the userText variable
},
),
),
OutlinedButton(
// this is triggered whenever we click on button
onPressed: () {
// validate and save the form
// display the text below the button
},
child: const Text('Submit'),
),
// this will display the userText only
// if user has clicked on submit button
if (hasSubmitted) Text(userText)
],
);
}
}
Steps:
- User types in the text-field
- User clicks submit button
-
onPressed
function of submit button is triggered - Inside
onPressed
function:- Validate and save the form
- This will trigger the
onSaved
function in theTextFormField
- Inside
onSaved
function:- Store the text field's value in the
userText
variable
- Store the text field's value in the
- Update
hasSubmitted
variable withtrue
Implementation:
import 'package:flutter/material.dart';
class MyForm extends StatefulWidget {
const MyForm({Key? key}) : super(key: key);
@override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
String userText = "";
bool hasSubmitted = false;
// for getting access to form
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Column(
children: [
Form(
// attaching key to form
key: _formKey,
child: TextFormField(
onSaved: (value) {
/// updating userText variable
if (value != null) userText = value;
},
),
),
OutlinedButton(
onPressed: () {
// validating form
if (!_formKey.currentState!.validate()) {
return;
}
// saving form
_formKey.currentState!.save();
// updating hasSubmitted
hasSubmitted = true;
},
child: const Text('Submit'),
),
if (hasSubmitted) Text(userText)
],
);
}
}
There's one small step left.
When you run this program, you'll notice that nothing happens when we click on submit button.
Here setState
comes to the rescue!
Now the question is where should we call it?
There are two places in the program where we are updating variables.
- inside
onSaved
function - insided
onPressed
function
So either one of these or both places should be the answer.
Let's ask one question to ourselves.
"On which variable update, do I want to update the UI of the screen?"
Is it userText
inside onSaved
function
or
hasSubmitted
inside onPressed
function?
You got it right!
It's inside the onPressed
function after the hasSubmitted
variable has been updated.
...
onPressed: () {
// validating form
if (!_formKey.currentState!.validate()) {
return;
}
// saving form
_formKey.currentState!.save();
// updating hasSubmitted
hasSubmitted = true;
setState(() {});
},
...
Again, this is same as below:
onPressed: () {
// validating form
if (!_formKey.currentState!.validate()) {
return;
}
// saving form
_formKey.currentState!.save();
setState(() {
// updating hasSubmitted
hasSubmitted = true;
});
},
Why use setState
here?
In our logic, we used hasSubmitted
variable as a condition to show the userText
. So only after updating hasSubmitted
value, does the UI show our desired result.
一歩前進:
- What happens when you use the
setState
inside theonSaved
function only?
...
onSaved: (value) {
/// updating userText variable
if (value != null) userText = value;
setState(() {});
},
),
),
OutlinedButton(
onPressed: () {
// validating form
if (!_formKey.currentState!.validate()) {
return;
}
// saving form
_formKey.currentState!.save();
// updating hasSubmitted
hasSubmitted = true;
},
...
It works here also. Surprise!!
But why? This goes against everything we've read so far in this blog.
So here's what happens.
When we call setState
, the Widget inside we called it is marked as dirty
.
Now whenever the framework actually rebuilds the UI of the screen, it will take into account all the latest values of the respective variables and paint the pixels on the screen.
This happens 60 times per second usually, which is the frame per second (fps) rate of flutter. That means approximately every 16ms (1000/60 ms).
次のフレームがレンダリングされるまで他の変更がある場合は、それらの変更も画面のUIに反映されます.
変化
hasSubmitted
変数はこのような場合に該当します.どうやってそれを確かめるの?
印刷文を追加して、UIが実際にいつ再構築されるかを正確に見ましょう.
import 'package:flutter/material.dart';
class MyForm extends StatefulWidget {
const MyForm({Key? key}) : super(key: key);
@override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
String userText = "";
bool hasSubmitted = false;
// for getting access to form
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
print('Widget build called');
return Column(
children: [
Form(
// attaching key to form
key: _formKey,
child: TextFormField(
onSaved: (value) {
print('inside save');
if (value != null) userText = value;
print('hasSubmitted value before setState: $hasSubmitted');
setState(() {});
print('hasSubmitted value after setState: $hasSubmitted');
},
),
),
OutlinedButton(
onPressed: () async {
print('button clicked: ->');
// validating form
if (!_formKey.currentState!.validate()) {
return;
}
print('before calling save');
// saving form
_formKey.currentState!.save();
print('after calling save');
print('hasSubmitted value after calling save: $hasSubmitted');
// updating hasSubmitted
hasSubmitted = true;
print(
'hasSubmitted value after updating hasSubmitted: $hasSubmitted');
},
child: const Text('Submit'),
),
if (hasSubmitted) Text(userText)
],
);
}
}
これらの印刷文で、フラッタフレームワークがUIを更新する順序を見ることができます.テキストフィールドに何かを書きましょう.
インサイドコンソール
Widget build called
button clicked: ->
before calling save
inside save
hasSubmitted value before setState: false
hasSubmitted value after setState: false
after calling save
hasSubmitted value after calling save: false
hasSubmitted value after updating hasSubmitted: true
Widget build called
明らかに、ウィジェットはhasSubmitted
がtrue
.だから大きな問題は次のとおりです
setState
内部onSaved
関数の内部ではなくonPressed
機能がうまくいくようです."このメソッドはすべてのユースケースでは動作しません.そしてそれも論理的に間違っている.
コードをリファクタ(新しい機能を追加するように)に戻ると、物事は期待通りに動作しないかもしれません.問題が古いコードにあるので、コードをデバッグするのも難しいでしょう.
そのようなユースケースの例を見ましょう.
Okay, we're almost done. This is the most important part. We've been building up to this point since the starting of this blog.
この例の手順に戻りましょう.
userText
データは、最小20 ms(これはあなたの選択の任意の期間)することができます.我々の望ましい結果は、まだ起こりますか?
onPressed: () async {
print('button clicked: ->');
// validating form
if (!_formKey.currentState!.validate()) {
return;
}
print('before calling save');
// saving form
_formKey.currentState!.save();
print('after calling save');
print('hasSubmitted value after calling save: $hasSubmitted');
/// some computation that takes 20ms
await Future.delayed(const Duration(milliseconds: 20), () {});
// updating hasSubmitted after 20ms
hasSubmitted = true;
print(
'hasSubmitted value after updating hasSubmitted: $hasSubmitted');
},
内部を見るonPressed
関数.我々はawating
エーFuture
そして、hasSubmitted
値.If you don't know what await, async and Future means, then just think that we're basically saying to program, "Hey flutter framework, we're gonna do some task that'll probably take some time. Please do it in the next iteration."
I'll encourage you to read about asynchronous programming. This concept is not exclusive to dart.
私たちはまたTimer 同じ効果を得るには、ここで使用しますFuture .
インサイドコンソール
Widget build called
button clicked: ->
before calling save
inside save
hasSubmitted value before setState: false
hasSubmitted value after setState: false
after calling save
hasSubmitted value after calling save: false
Widget build called
hasSubmitted value after updating hasSubmitted: true
今、我々は画面上でUIの更新を表示されません.およびprint文の順序に従ってhasSubmitted
変数は、ウィジェットが再構築された後に更新されます.理由
Warning: This includes some advanced topics. I'll try to explain it as simply as possible.
があるqueue マイクロタスクつずつすべてのタスクはダートで行われます.使用するとき
await
, 私たちは最初にダーツを教えて、現在のタスク(20 msを待って)を完了し、次にキュー内の次のいずれかに移動します.いくつかのタスク(画面のレンダリングと20 ms待ち)が同時に実行されているが、以下のタスクは
await
キーワード更新hasSubmitted
は、現在のタスクが完了するまで実行されません.だから、フレームワークが実際にレンダリングされたとき
dirty
ウィジェット( myform )hasSubmitted
変数の値は更新されませんでした.したがって、送信ボタンの下に入力されたテキストが表示されません.探すべきもの
また、ある
event queue
. 我々は未来が完了するのを待つが、次のマイクロタスクに継続したい場合は、将来的にタスクが追加されますevent queue
.もっと実験したいですか?
期間を0秒に変更してみてください.
UIはまだ必要に応じて更新されません.
If you wanna dig deep into why after encountering the
await
keyword, does the code below it doesn't run synchronously even if the duration is 0 seconds, then read about the event loop in dart. There are other resources also that you can easily find on the internet. This concept is not exclusive to dart.
この新しいユースケース(将来を使用する)で、我々の望ましい出力は達成されません.以下は短い要約です.
/// Approach 1: this is good (recommended)
setState((){
hasSubmitted = true;
});
...
/// Approach 2: this is also good
hasSubmitted = true;
setState((){});
...
/// Approach 3: this is not good
setState((){});
hasSubmitted = true;
setState
両方の機能の中?私たちの目的の結果が達成されるので、我々は
setState
インサイドonSaved
関数.私は今あなたがいつ使用しないかという考えを得たことを望みます
setState
.これは2つの変数だけを考えた非常に簡単な例でしたので、コードにロジックを実装するのは簡単です.
プログラムが大きく複雑になると、変数とUIの更新の追跡が面倒になります.
それから、我々はstatefulwidgetのものの混合を使います
setState
その他のstate management 解決法フラッタのオフィシャルサイトをお読みしましょうdocs そして、この上でグリップを得るためにアプリをビルドします.
概要
-
setState
is a way to dynamically change the UI. - We call it inside the State Object class of the StatefulWidget.
- Calling
setState
marks the corresponding Widgetdirty
. When flutter builds the next frame (approx. every 16ms), it renders the Widget according to the latest values of the State Object. - Where we call
setState
matters a lot. - There are other state management solutions also.
ファイナルノート
Thank you for reading this article. If you enjoyed it, consider sharing it with other people.
If you find any mistakes, please let me know.
Feel free to share your opinions below.
Reference
この問題について(どのようなときにflutterでsetstat ()とは何を使用するか?), 我々は、より多くの情報をここで見つけました https://dev.to/nicks101/when-to-use-setstate-in-flutter-380テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol