简体   繁体   中英

Flutter BloC not refresh my view (counter app)

I'm trying BloC library with Counter test app but I have a little problem on my view when I use object to increment/decrement my counter. MyObject.MyProperty increment or decrement but my view not refreshed. Why?

Sample app with simple int variable.

My code with object:

import 'dart:async';    
import 'package:flutter/material.dart';    
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';    

void main() { 
  runApp(App());
}

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: BlocProvider(
        builder: (context) => CounterBloc(),
        child: CounterPage(),
      ),
    );
  }
}

class TestTest{
  int myProperty; 

  TestTest(){
    myProperty = 0;
  }
}


class CounterPage extends StatefulWidget {
  @override
  _CounterPageState createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  @override
  Widget build(BuildContext context) {
    final counterBloc = BlocProvider.of<CounterBloc>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: BlocBuilder<CounterBloc, TestTest>(
        builder: (context, myClassTestTest) { 
          return Center(
            child: Text(
              '${myClassTestTest.myProperty}',
              style: TextStyle(fontSize: 24.0),
            ),
          );
        },
      ),
      floatingActionButton: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: () {
                counterBloc.dispatch(CounterEvent.increment);
              },
            ),
          ),
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              child: Icon(Icons.remove),
              onPressed: () {
                counterBloc.dispatch(CounterEvent.decrement);
              },
            ),
          ),
        ],
      ),
    );
  }
}


enum CounterEvent { increment, decrement }

class CounterBloc extends Bloc<CounterEvent, TestTest> {
  @override
  TestTest get initialState => TestTest();

  @override
  Stream<TestTest> mapEventToState(CounterEvent event) async* {
    switch (event) {
      case CounterEvent.decrement:        
        currentState.myProperty -= 1;
        yield currentState; 
        break;
      case CounterEvent.increment:
        currentState.myProperty += 1;
        yield currentState; 
        break;
    }
  }
}

As you can see I edited the sample app a little just to add my class TestTest with a int property myProperty . When I clic on my button the value in my object change but my view is not refreshed.

How can I fix that?

I found a solution to recreate my TestTest in mapEventToState but it's weird to recreate an entire object just to update a property...

Thank you

Just posting another possible solution for future visitors.

If you do yield for the same state twice. Your UI won't update.

For example if you have already triggered ShowDataToUser(data) from your Bloc and now after another operation you are yielding ShowDataToUser(data) again, your UI won't update.

A quick hack around this would be to just create a DummyState() in your Bloc and yield DummyState() everytime before you yield ShowDataToUser(data)

Another solution apart from the quick hack around is to define a way for dart to compare and equate a class, cos currently by default ShowDataToUser is the same class, but its contents are different, but Dart doesn't know how to evaluate the content and compare them.

Enters Equatable

For this, you need to use the package : https://pub.dev/packages/equatable

Now you have to extend your Bloc states with Equtable like:

class DummyState extends Equatable {}

Now, if your Bloc States are extending Equatable then you must override the function:

@override
  List<Object> get props => [name];

Here name is the property/field name you want to compare when comparing two same classes.

This function tells your Bloc, how to differentiate between two same states (which might have different values in them).

I see you are using some kind of redux implementation and i'm not quite sure how this handles the rebuild when actions are dispatched.

As my knowledge you are dispatching an action that update some property on the widget but you are never calling setState again, so the widget is not being rebuild.

Try to call the increment or decrement actions inside a setState function.

Something like this:

onPressed: () {
 setState(() => counterBloc.dispatch(CounterEvent.decrement));
},

I have had same problem, and after googling couple of hour, I have found the solution in https://github.com/felangel/bloc/issues/203#issuecomment-480619554

As shown in the below code, The main problem is that when you yield the state, the previous state and the current state are equal, so blocBuilder have hadn't triggered.

case CounterEvent.decrement:        
  currentState.myProperty -= 1;
  yield currentState; 

You should copy the state, then change and yield it.

final TestTest newState = TestTest();
newState.myProperty = currentState.myProperty;

...

case CounterEvent.decrement:        
  newState.myProperty -= 1;
  yield newState; 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM