Flutter - как перезагрузить всю страницу (снова перезагрузить виджет с его initstate) при вызове кнопки действия? - PullRequest
1 голос
/ 02 августа 2020

У меня есть виджет с отслеживанием состояния, содержащий настраиваемое представление вкладок.

При инициализации виджета данные категории (здесь «Все», «Наука», «Тенденции», «Здоровье и фитнес») извлекаются из хранилища данных и, соответственно, соответствующих виджетов. добавляются в представление вкладок.

Чтобы добавить новую категорию, ее можно выбрать из последней вкладки (здесь "+"). Внутри соответствующего виджета можно выбрать интересующие его категории.

При нажатии кнопки «Добавить категорию» я добавляю выбранную категорию в firestore, но после этого я хочу перезагрузить виджет и загрузить его состояние инициализации (поскольку данные категории выбираются в initstate).

Кто-нибудь, пожалуйста, объясните, как я могу добиться того же здесь?

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_advanced_networkimage/provider.dart';
import 'package:flutter_advanced_networkimage/transition.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:journey_togather/pages/home.dart';
import 'package:journey_togather/pages/invite_friends.dart';
import 'package:journey_togather/pages/journey/create_journey.dart';
import 'package:journey_togather/pages/journey/create_paid_journey.dart';
import 'package:journey_togather/pages/journey/journey_home.dart';
import 'package:journey_togather/pages/locator.dart';
import 'package:journey_togather/services/analytics.dart';
import 'package:journey_togather/settings/appbuilder.dart';
import 'package:journey_togather/settings/sizeconfig.dart';
import 'package:journey_togather/settings/textstyle.dart';
import 'package:journey_togather/widgets/all_user_journey.dart';
import 'package:journey_togather/widgets/pillButton.dart';
import 'package:journey_togather/widgets/progress.dart';
import 'package:uuid/uuid.dart';

class ExploreFeed extends StatefulWidget {
  @override
  _ExploreFeedState createState() => _ExploreFeedState();
}

class _ExploreFeedState extends State<ExploreFeed>
    with TickerProviderStateMixin {
  bool isLoading = false;

  final AnalyticsService _analyticsService = locator<AnalyticsService>();
  TabController tabController;

  final _scaffoldKey = GlobalKey<ScaffoldState>();

  // this will control the button clicks and tab changing
  TabController _controller;
  // this will control the animation when a button changes from an off state to an on state
  AnimationController _animationControllerOn;

  // this will control the animation when a button changes from an on state to an off state
  AnimationController _animationControllerOff;

  // this will give the background color values of a button when it changes to an on state
  Animation _colorTweenBackgroundOn;
  Animation _colorTweenBackgroundOff;

  // this will give the foreground color values of a button when it changes to an on state
  Animation _colorTweenForegroundOn;
  Animation _colorTweenForegroundOff;

  // when swiping, the _controller.index value only changes after the animation, therefore, we need this to trigger the animations and save the current index
  int _currentIndex = 0;

  // saves the previous active tab
  int _prevControllerIndex = 0;

  // saves the value of the tab animation. For example, if one is between the 1st and the 2nd tab, this value will be 0.5
  double _aniValue = 0.0;

  // saves the previous value of the tab animation. It's used to figure the direction of the animation
  double _prevAniValue = 0.0;

  List<dynamic> _userInterestCategory = ['All'];
  List<dynamic> _userInterestCategoryId = ['All'];
  List<Widget> _interestFeedBuild = [];

  //saves the newly added interest data of a user from add category tab
  List _interestDataId = [];
  List _interestDataName = [];

  // store the pill buttons for categories in add category page
  List<Widget> buttons;

  Color _foregroundOn = Colors.white;
  Color _foregroundOff = Colors.grey;

  // active button's background color
  Color _backgroundOn = Colors.green;
//  Color _backgroundOff = Colors.black;
  Color _backgroundOff = Colors.grey.withOpacity(0.1);

  // scroll controller for the TabBar
  ScrollController _tabScrollController = new ScrollController();

  // this will save the keys for each Tab in the Tab Bar, so we can retrieve their position and size for the scroll controller
  List _keys = [];

  bool _buttonTap = false;
//  ScrollController _scrollController = ScrollController();

  @override
  void initState() {
    setState(() {
      isLoading = true;
    });
    _initialiseData();
//    WidgetsBinding.instance.addPostFrameCallback(_initialiseData());
    super.initState();
  }

  _initialiseData()async{
    _getInterestData().whenComplete(() {
      for (int index = 0; index < _userInterestCategory.length; index++) {
        // create a GlobalKey for each Tab
        _keys.add(new GlobalKey());
      }

      // this creates the controller with 6 tabs (in our case)
      _controller =
          TabController(vsync: this, length: _userInterestCategory.length);
      // this will execute the function every time there's a swipe animation
      _controller.animation.addListener(_handleTabAnimation);
      // this will execute the function every time the _controller.index value changes
      _controller.addListener(_handleTabChange);

      _animationControllerOff = AnimationController(
          vsync: this, duration: Duration(milliseconds: 75));
      // so the inactive buttons start in their "final" state (color)
      _animationControllerOff.value = 1.0;
      _colorTweenBackgroundOff =
          ColorTween(begin: _backgroundOn, end: _backgroundOff)
              .animate(_animationControllerOff);
      _colorTweenForegroundOff =
          ColorTween(begin: _foregroundOn, end: _foregroundOff)
              .animate(_animationControllerOff);

      _animationControllerOn = AnimationController(
          vsync: this, duration: Duration(milliseconds: 150));
      // so the inactive buttons start in their "final" state (color)
      _animationControllerOn.value = 1.0;
      _colorTweenBackgroundOn =
          ColorTween(begin: _backgroundOff, end: _backgroundOn)
              .animate(_animationControllerOn);
      _colorTweenForegroundOn =
          ColorTween(begin: _foregroundOff, end: _foregroundOn)
              .animate(_animationControllerOn);

      _userInterestCategoryId.forEach((element) {
        if (element != '+') {
          _interestFeedBuild.add(
            BuildInterestFeed(
              categoryId: element,
            ),
          );
        } else {
          buildButton();
          _interestFeedBuild.add(
            _buildAddInterest(),
          );
        }
      });
      setState(() {
        isLoading = false;
      });
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  _getInterestData() async {
    DocumentSnapshot _doc =
        await userInterestsRef.document(currentUser.id).get();
    List _interestsFetched = _doc.data['categories'];
    _interestsFetched = _interestsFetched.toSet().toList();
    Map _userInterestData = {};
    for (var val in _interestsFetched) {
      Map _filteredData =
          globalInterest.firstWhere((element) => element['categoryId'] == val);
      _userInterestData[_filteredData['categoryId']] =
          (_filteredData['categoryName']);
    }
    setState(() {
      _userInterestCategory =
          _userInterestCategory + _userInterestData.values.toList() + ['+'];
      _userInterestCategoryId =
          _userInterestCategoryId + _userInterestData.keys.toList() + ['+'];
    });
  }

  //tab bar functions
  // runs during the switching tabs animation
  _handleTabAnimation() {
    // gets the value of the animation. For example, if one is between the 1st and the 2nd tab, this value will be 0.5
    _aniValue = _controller.animation.value;

    // if the button wasn't pressed, which means the user is swiping, and the amount swipped is less than 1 (this means that we're swiping through neighbor Tab Views)
    if (!_buttonTap && ((_aniValue - _prevAniValue).abs() < 1)) {
      // set the current tab index
      _setCurrentIndex(_aniValue.round());
    }

    // save the previous Animation Value
    _prevAniValue = _aniValue;
  }

  // runs when the displayed tab changes
  _handleTabChange() {
    // if a button was tapped, change the current index
    if (_buttonTap) _setCurrentIndex(_controller.index);

    // this resets the button tap
    if ((_controller.index == _prevControllerIndex) ||
        (_controller.index == _aniValue.round())) _buttonTap = false;

    // save the previous controller index
    _prevControllerIndex = _controller.index;
  }

  _setCurrentIndex(int index) {
    // if we're actually changing the index
    if (index != _currentIndex) {
      setState(() {
        // change the index
        _currentIndex = index;
      });

      // trigger the button animation
      _triggerAnimation();
      // scroll the TabBar to the correct position (if we have a scrollable bar)
      _scrollTo(index);
    }
  }

  _triggerAnimation() {
    // reset the animations so they're ready to go
    _animationControllerOn.reset();
    _animationControllerOff.reset();

    // run the animations!
    _animationControllerOn.forward();
    _animationControllerOff.forward();
  }

  _scrollTo(int index) {
    // get the screen width. This is used to check if we have an element off screen

    double screenWidth = MediaQuery.of(context).size.width;

    // get the button we want to scroll to

    RenderBox renderBox = _keys[index].currentContext.findRenderObject();
    // get its size
    double size = renderBox.size.width;
    // and position
    double position = renderBox.localToGlobal(Offset.zero).dx;

    // this is how much the button is away from the center of the screen and how much we must scroll to get it into place
    double offset = (position + size / 2) - screenWidth / 2;

    // if the button is to the left of the middle
    if (offset < 0) {
      // get the first button
      renderBox = index-1 < 0 ?  _keys[0].currentContext.findRenderObject() : _keys[index - 1].currentContext.findRenderObject();
      // get the position of the first button of the TabBar
      position = renderBox.localToGlobal(Offset.zero).dx;

      // if the offset pulls the first button away from the left side, we limit that movement so the first button is stuck to the left side
      if (position > offset) offset = position;
    } else {
      // if the button is to the right of the middle

      // get the last button
      renderBox =  index+1 == _userInterestCategory.length ? _keys[_userInterestCategory.length-1]
          .currentContext
          .findRenderObject(): _keys[index + 1]
          .currentContext
          .findRenderObject();
      // get its position
      position = renderBox.localToGlobal(Offset.zero).dx;
      // and size
      size = renderBox.size.width;

      // if the last button doesn't reach the right side, use it's right side as the limit of the screen for the TabBar
      if (position + size < screenWidth) screenWidth = position + size;

      // if the offset pulls the last button away from the right side limit, we reduce that movement so the last button is stuck to the right side limit
      if (position + size - offset < screenWidth) {
        offset = position + size - screenWidth;
      }
    }

    // scroll the calculated ammount
    _tabScrollController.animateTo(offset + _tabScrollController.offset,
        duration: new Duration(milliseconds: 150), curve: Curves.easeInOut);
  }

  _getBackgroundColor(int index) {
    if (index == _currentIndex) {
      // if it's active button
      return _colorTweenBackgroundOn.value;
    } else if (index == _prevControllerIndex) {
      // if it's the previous active button
      return _colorTweenBackgroundOff.value;
    } else {
      // if the button is inactive
      return _backgroundOff;
    }
  }

  _getForegroundColor(int index) {
    // the same as the above
    if (index == _currentIndex) {
      return _colorTweenForegroundOn.value;
    } else if (index == _prevControllerIndex) {
      return _colorTweenForegroundOff.value;
    } else {
      return _foregroundOff;
    }
  }

  callback(Map data) {
    if (data['method'] == 'delete') {
      setState(() {
        _interestDataId.removeWhere((element) => element == data['id']);
        _interestDataName.removeWhere((element) => element == data['name']);
      });
    } else {
      setState(() {
        _interestDataId.add(data['id']);
        _interestDataName.add(data['name']);
      });
    }
  }

  addInterest(context) async {
    if(_interestDataId.length > 0 && _interestDataName.length > 0) {
      await userInterestsRef
          .document(currentUser.id)
          .updateData({'categories': FieldValue.arrayUnion(_interestDataId)});


      Navigator.of(context).pushReplacement(
        MaterialPageRoute(
          builder: (context) {
            return Home();
          },
        ),
      );

    }
    else{
      SnackBar snackbar = SnackBar(
        content: Text("Please select atleast 1 interest before submitting"),
      );
      _scaffoldKey.currentState.showSnackBar(snackbar);
    }

  }

  buildButton() {
    List<Widget> _buttons = [];
    globalInterest.forEach((element) {
      if (_userInterestCategoryId.contains(element['categoryId'])) {
      } else {
        _buttons.add(PillButton(
          interestId: element['categoryId'],
          interestTitle: element['categoryName'],
          callback: callback,
        ));
      }
    });
    setState(() {
      buttons = _buttons;
    });
  }

  Widget _buildAddInterest() {
    return Scaffold(
      bottomNavigationBar: buttons.length != 0 ?
      Container(
        margin: EdgeInsets.only(bottom: 16.0),
        padding: EdgeInsets.symmetric(horizontal: 16.0),
        height: 40,
        child: FlatButton(
          onPressed: () => addInterest(context),
          color: Colors.black,
          child: Text(
            'Add category',
            style: Theme.of(context)
                .textTheme
                .subtitle2
                .copyWith(color: Theme.of(context).primaryColor),
          ),
        ),
      ) : Container(),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16.0),
        child: buttons.length != 0
            ? Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  Wrap(
                    spacing: 12.0,
                    children: buttons,
                  ),
                ],
              )
            : Center(
                child: Text(
                  'Voila! You\'ve selected all the available interests.',
                  style: Theme.of(context).textTheme.subhead.copyWith(
                      color:
                          Theme.of(context).primaryColorDark.withOpacity(0.40),),
//                            Colors.black,)
                ),
              ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return isLoading
        ? circularProgress(context)
        : AppBuilder(
      builder: (context){
        return Scaffold(
            key: _scaffoldKey,
            appBar: AppBar(
                automaticallyImplyLeading: false,
                backgroundColor: Theme.of(context).primaryColor,
                elevation: 0,
                title: IconButton(
                  iconSize: SizeConfig.safeBlockHorizontal * 21,
                  icon: Image.asset(
                    Theme.of(context).brightness == Brightness.light
                        ? 'assets/images/logo.png'
                        : 'assets/images/journey_exploreFeed_dark.png',
                  ),
                ),
                titleSpacing: 8.0,
                actions: <Widget>[
                  IconButton(
                    padding: EdgeInsets.only(right: 24),
                    icon: new Image.asset(
                        Theme.of(context).brightness == Brightness.light
                            ? 'assets/icons/create_journey_black.png'
                            : 'assets/icons/create_journey_white.png'),
                    onPressed: () {
                      _analyticsService.logCreateJourneyExploreFeed();
                      Navigator.push(
                        context,
                        MaterialPageRoute(
                            builder: (context) => CreateJourney()),
                      );
                    },
                  ),
                  IconButton(
                    padding: EdgeInsets.only(right: 16),
                    icon:
                    new Image.asset('assets/icons/invite_friends_icon.png'),
                    onPressed: () {
                      _analyticsService.logShareApp();
                      Navigator.push(
                        context,
                        MaterialPageRoute(
                            builder: (context) => InviteFriends()),
                      );
                    },
                  ),
                ]),
            backgroundColor: Colors.transparent,
            body: Column(children: <Widget>[
              // this is the TabBar
              Container(
                  height: 52.0,
                  color: Theme.of(context).primaryColor,
                  // this generates our tabs buttons
                  child: ListView.builder(
                    // this gives the TabBar a bounce effect when scrolling farther than it's size
                      physics: BouncingScrollPhysics(),
                      controller: _tabScrollController,
                      // make the list horizontal
                      scrollDirection: Axis.horizontal,
                      // number of tabs
                      itemCount: _userInterestCategory.length,
                      itemBuilder: (BuildContext context, int index) {
                        return Padding(
                          // each button's key
                            key: _keys[index],
                            // padding for the buttons
                            padding: EdgeInsets.fromLTRB(8.0, 4.0, 0.0, 8.0),
                            child: ButtonTheme(
                                child: AnimatedBuilder(
                                  animation: _colorTweenBackgroundOn,
                                  builder: (context, child) => FlatButton(
                                    // get the color of the button's background (dependent of its state)
                                      color: _getBackgroundColor(index),
                                      padding:
                                      EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0),
                                      // make the button a rectangle with round corners
                                      shape: RoundedRectangleBorder(
                                          borderRadius:
                                          new BorderRadius.circular(24.0)),
                                      onPressed: () {
                                        setState(() {
                                          _buttonTap = true;
                                          // trigger the controller to change between Tab Views
                                          _controller.animateTo(index);
                                          // set the current index
                                          _setCurrentIndex(index);
                                          // scroll to the tapped button (needed if we tap the active button and it's not on its position)
                                          _scrollTo(index);
                                        });
                                      },
                                      child: index !=
                                          _userInterestCategory.length - 1
                                          ? Text(
                                        // get the icon
                                        _userInterestCategory[index],
                                        // get the color of the icon (dependent of its state)
                                        style: (TextStyle(
                                          color: _getForegroundColor(index),
                                        )),
                                      )
                                          : Icon(
                                        Icons.add,
                                        color: _getForegroundColor(index),
                                      )),
                                )));
                      })),
              Flexible(
                // this will host our Tab Views
                  child: TabBarView(
                    // and it is controlled by the controller
                    controller: _controller,
                    children: _interestFeedBuild,
                  )),
            ]));
      },
    );
  }
}

class BuildInterestFeed extends StatefulWidget {
  final String categoryId;

  BuildInterestFeed({this.categoryId});
  @override
  _BuildInterestFeedState createState() => _BuildInterestFeedState();
}

class _BuildInterestFeedState extends State<BuildInterestFeed>
    with AutomaticKeepAliveClientMixin {
  bool isLoading = false;
  List<Journey> journeys = [];
  final AnalyticsService _analyticsService = locator<AnalyticsService>();
  String docId = Uuid().v4();
  List<DocumentSnapshot> journeysFetched = []; // stores fetched products
  bool hasMore = true; // flag for more products available or not
  int documentLimit = 10; // documents to be fetched per request
  DocumentSnapshot
      lastDocument; // flag for last document from where next 10 records to be fetched
  ScrollController _verticalScrollController = ScrollController();

  @override
  void initState() {
    super.initState();

    if (widget.categoryId != '+') {
      _getExploreFeedData();
      _verticalScrollController.addListener(() {
        double maxScroll = _verticalScrollController.position.maxScrollExtent;
        double currentScroll = _verticalScrollController.position.pixels;
        double delta = MediaQuery.of(context).size.height * 0.10;
        if (maxScroll - currentScroll <= delta) {
          _getExploreFeedData();
        }
      });
    }
  }

  _getExploreFeedData() async {
    if (!hasMore) {
      print('No More Journeys');
      return;
    }
    if (isLoading) {
      return;
    }
    setState(() {
      isLoading = true;
    });
    if (widget.categoryId == 'All') {
      await buildAllDataFeed();
    } else {
      QuerySnapshot querySnapshot;
      if (lastDocument == null) {
        querySnapshot = await journeyRef
            .orderBy('createdAt', descending: true)
            .where('category', arrayContains: widget.categoryId)
            .limit(documentLimit)
            .getDocuments();
      } else {
        querySnapshot = await journeyRef
            .orderBy('createdAt', descending: true)
            .where('category', arrayContains: widget.categoryId)
            .startAfterDocument(lastDocument)
            .limit(documentLimit)
            .getDocuments();
      }
      if (querySnapshot.documents.length != 0) {
        if (querySnapshot.documents.length < documentLimit) {
          hasMore = false;
        }
        if (querySnapshot.documents.length != 0) {
          lastDocument =
              querySnapshot.documents[querySnapshot.documents.length - 1];
        } else {
          lastDocument = null;
        }
        journeysFetched.addAll(querySnapshot.documents);
      }
    }
    setState(() {
      isLoading = false;
    });
  }

  buildAllDataFeed() async {
    QuerySnapshot querySnapshot;
    if (lastDocument == null) {
      querySnapshot = await journeyRef
          .orderBy('createdAt', descending: true)
          .limit(documentLimit)
          .getDocuments();
    } else {
      querySnapshot = await journeyRef
          .orderBy('createdAt', descending: true)
          .startAfterDocument(lastDocument)
          .limit(documentLimit)
          .getDocuments();
    }
    if (querySnapshot.documents.length < documentLimit) {
      hasMore = false;
    }
    if (querySnapshot.documents.length != 0) {
      lastDocument =
          querySnapshot.documents[querySnapshot.documents.length - 1];
    } else {
      lastDocument = null;
    }
//    lastDocument = querySnapshot.documents[querySnapshot.documents.length - 1];
    journeysFetched.addAll(querySnapshot.documents);
  }

  addInterestData({tags}) async{
    userInterestsRef.document(currentUser.id).updateData({
      'tags': FieldValue.arrayUnion(tags),
      });
    }

  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return
    RefreshIndicator(
      onRefresh: () => _getExploreFeedData(),
      child:
      Scaffold(
        body: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 8.0),
          child: Column(children: [
            Expanded(
              child: journeysFetched.length == 0
                  ? Center(
                child: Text(
                  'There\'s nothing new here right now',
                  style: Theme.of(context).textTheme.subhead.copyWith(
                      color: Theme.of(context)
                          .primaryColorDark
                          .withOpacity(0.40)),
                ),
              )
                  : StaggeredGridView.countBuilder(
                controller: _verticalScrollController,
                crossAxisCount: 2,
                itemCount: journeysFetched.length,
                itemBuilder: (BuildContext context, int index) {
                  Map journeyData = journeysFetched.elementAt(index).data;
                  String journeyId =
                      journeysFetched.elementAt(index).documentID;
                  return GestureDetector(
                      child: Container(
                        child: Column(
                          mainAxisSize: MainAxisSize.min,
                          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: <Widget>[
                            TransitionToImage(
                              transitionType: TransitionType.fade,
                              borderRadius: BorderRadius.circular(8.0),
                              loadingWidget: Container(
                                  height: 242,
                                  decoration: (BoxDecoration(
                                    borderRadius:
                                    BorderRadius.circular(8.0),
                                    color: Theme.of(context)
                                        .primaryColorLight
                                        .withOpacity(0.05),
                                  ))),
                              image: AdvancedNetworkImage(
                                  journeyData['coverImage'],
                                  useDiskCache: true,
                                  cacheRule: CacheRule(
                                      maxAge: Duration(hours: 9999))),
                            ),
                            Padding(
                                padding: EdgeInsets.only(
                                    top: SizeConfig.safeBlockVertical * 0.5,
                                    left:
                                    SizeConfig.safeBlockVertical * 0.5),
                                child: Text(
                                  journeyData['title'],
                                  style: Theme.of(context)
                                      .textTheme
                                      .bodyText2
                                      .copyWith(
                                      fontWeight: FontWeight.w500),
                                ))
                          ],
                        ),
                      ),
                      onTap: () {
                      addInterestData(
                          tags: journeyData['community']['hashtags']);
                        Navigator.push(
                            context,
                            MaterialPageRoute(
                                settings:
                                RouteSettings(name: 'journey_home'),
                                builder: (context) =>
                                    JourneyHome(journeyId: journeyId)));
                      });
                },
                staggeredTileBuilder: (int index) => StaggeredTile.fit(1),
                mainAxisSpacing: SizeConfig.safeBlockVertical * 2,
                crossAxisSpacing: 8.0,
              ),
            ),
            isLoading ? circularProgress(context) : Container()
          ]),
        ),
      ),
    );
  }
}

Снимок экрана

1 Ответ

0 голосов
/ 02 августа 2020
There is also a pub package named states_rebuilder

https://pub.dev/packages/states_rebuilder

или

наш виджет должен иметь метод setState(), этот метод вызывается, виджет перерисовывается

...