My Project was working totally fine a month ago, and now after several flutter updates, my iFrame widget is false centered in its desired renderbox. First picture is the wrong behavior, second is the old working version. I'm using a plugin which prevents the analyzer from prompting errors when using platformViewRegistry. Below is my code for the iframe-widget.
Does someone know how to fix this? I don't want to downgrade to older flutter versions.
Thanks for any help!
PS: Simple Center() did not work
My IFrame Widget
// ignore: avoid_web_libraries_in_flutter
import 'dart:html';
// ignore: undefined_prefixed_name
import 'package:universal_ui/universal_ui.dart';
import 'package:flutter/material.dart';
class Iframe extends StatefulWidget {
final String source;
final Size size;
Iframe(this.source, {this.size});
#override
_IframeState createState() => _IframeState();
}
class _IframeState extends State<Iframe> {
Widget _iframeWidget;
String source;
#override
void initState() {
newFrame();
super.initState();
}
void newFrame() async {
print(widget.size);
final String id = widget.source.hashCode.toString();
final IFrameElement _iframeElement = IFrameElement();
_iframeElement.height = widget.size?.height?.toString() ?? '500';
_iframeElement.width = widget.size?.width?.toString() ?? '500';
source = widget.source;
_iframeElement.src = widget.source;
_iframeElement.style.border = 'none';
ui.platformViewRegistry
.registerViewFactory(id, (int viewID) => _iframeElement);
_iframeWidget = HtmlElementView(
key: UniqueKey(),
viewType: id,
);
}
#override
Widget build(BuildContext context) {
if (source != widget.source) newFrame();
return _iframeWidget;
}
}
Using IFrame Widget
class ChatClientAnon extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamCacheBuilder<Package>(
stream: Database().streamPackage(),
builder: (data) => Container(
child: Row(
children: [
Expanded(child: SmartphoneClient('Anonym', isAnon: true), flex: 2),
Expanded(
child: LayoutBuilder(
builder: (_, c) =>
Iframe(data.source, size: c.biggest)),
flex: 8),
],
),
),
);
}
}
Update: issue got fixed with next flutter update
Related
i'm trying to extract the ModalRoute as a Global value in a StatefulWidget but it's not working, i can extract it locally under Widget build(BuildContext context) and it will work but the Global methods and widgets that i'm working on wont work, please help :'(
Here is my code,
it starts from here:
home.dart
GestureDetector(
onTap: ()async{
await Navigator.of(context).pushNamed(MainTankHomePage.routeName, arguments: widget.tankID);
//widget.tankID is a String and i extracted it in MainTankHomePage.dart with ModalRoute as a String it works perfectly so no need to change anything here
},
MainTankHomePage.dart
import 'package:flutter/material.dart';
import 'package:animations/animations.dart';
import 'package:smart_tank1/main_tank_detail_ui/home/bottom_nav_bar.dart';
import 'package:smart_tank1/main_tank_detail_ui/hydration_pool/hydration_pool_page.dart';
import 'package:smart_tank1/main_tank_detail_ui/hydration_progress/hydration_progress_page.dart';
import 'package:smart_tank1/main_tank_detail_ui/summary/summary_page.dart';
class MainTankHomePage extends StatefulWidget {
static const routeName = 'main-screen';
#override
_MainTankHomePageState createState() => _MainTankHomePageState();
}
class _MainTankHomePageState extends State<MainTankHomePage> {
//------------------------------------------
//Here is my global methods that i worked on
//------------------------------------------
late final tanksID = ModalRoute.of(context)!.settings.arguments as String; // added late to get the (context) work without error line but it didn't work
final _pages = <Widget>[
//----------------------------------------
//Here is the problem that i'm facing!
//Done all of the parameters work in each widget with a required tankID
//So what i need here is just passing the extracted ModalRoute here which is the tanksID to each widget but it's not working
//-----------------------------------------
MainTankHydrationPoolPage(tankID: tanksID,),
MainHydrationProgressPage(tankID: tanksID,),
SummaryPage(tanksID: tanksID),
//-----------------------------
//Here is the error i get
//String tanksID
//package:smart_tank1/main_tank_detail_ui/home/main_tank_home_page.dart
//The instance member 'tanksID' can't be accessed in an initializer.
//Try replacing the reference to the instance member with a different //expression
//-------------------------------
];
int _currentPage = 0;
void _changePage(int index) {
if (index == _currentPage) return;
setState(() {
_currentPage = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
PageTransitionSwitcher(
transitionBuilder: (
child,
primaryAnimation,
secondaryAnimation,
) {
return FadeThroughTransition(
fillColor: Theme.of(context).backgroundColor,
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
child: child,
);
},
child: _pages[_currentPage],
),
BottomNavBar(
currentPage: _currentPage,
onChanged: _changePage,
),
],
),
);
}
}
It is possible to get null value from ModalRoute, I will suggest using nullable data.
late final String? tanksID = ModalRoute.of(context)?.settings.arguments as String?;
And pass default value while it gets null
MainTankHydrationPoolPage(tankID: tanksID??"default id",),
Or ignore build
if(tanksID!=null) MainTankHydrationPoolPage(tankID: tanksID!,),
Make sure checking null before using !.
late List<Widget> _pages;
#override
void initState() {
_pages = [
.....,
];
super.initState();
}
Check more about null-safety and check this answer.
I got recaptcha v2 working on flutter web but the issue is that I have to specify the initial height of the SizedBox that holds the HtmlElementView as follows
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'dart:html' as html;
class GoogleCaptchaView {
static registerViewFactory(String viewId, dynamic cb) {
// ignore:undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(viewId, cb);
}
}
class GCaptchaPlugin extends StatefulWidget {
const GCaptchaPlugin({Key? key}) : super(key: key);
#override
_GCaptchaPluginState createState() => _GCaptchaPluginState();
}
class _GCaptchaPluginState extends State<GCaptchaPlugin> {
String createdViewId = 'column';
#override
void initState() {
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(
createdViewId,
(int viewId) => html.IFrameElement()
..style.height = double.maxFinite.toString()
..style.width = double.maxFinite.toString()
..src = "/assets/html/recaptcha.html"
..style.border = 'none');
super.initState();
}
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
child: SizedBox(
height: 120.0,
child: HtmlElementView (
viewType: createdViewId,
),
),
),
],
mainAxisAlignment: MainAxisAlignment.spaceBetween,
);
}
}
the issue is I cannot find any documentation on how to resize the SizedBox to match the new size when the image picker shows up in reCaptcha V2 so I am stuck with something looking like this
reCaptcha not Resizing
Using flutter 1.20.4
I'm trying to implement a custom digit keyboard (a simple container at the bottom), which should appear into view when tapping on one of the rows in the List.
Here a small snippet of how I do it:
Widget build(BuildContext context) {
BudgetPageState budgetPageState = Provider.of<BudgetPageState>(context, listen: true);
ButtonDial buttonDial =
budgetPageState.showButtonDial ? ButtonDial() : null;
return Scaffold(
appBar: AppBar(
title: Text("Budget Page"),
),
body: Column(children: <Widget>[
Expanded(child: CustomList()),
if (buttonDial != null) buttonDial
]));
}
}
However, when the keyboard appears, the bottom rows get obscured by the container.
I tried using Scrollable.ensureVisible, that works for the middle rows, but the last ones are still obscured. It seems like the ScrollView still has it's old size (full height) when Scrollable.ensureVisible() kicks in (I notice this by looking at the ScrollBar).
Code snippet:
Scrollable.ensureVisible(context, duration: Duration(milliseconds: 200), alignment: 0.5);
See video below.
Keyboard obscures last rows when tapped (here clicking on row 14)
However, once the keyboard is up, the SingleChildScrollView has shrunk to the new size and the Scrollable now works.
When keyboard is up, Scrollable.ensureVisible() does its job(here clicking on row 6 and 12)
I know this is similar to this question, but
I tried multiple things of this issue.
I use a "custom keyboard"
The flutter github issue here below fixed this (I think)
Read through this popular Flutter Github issue, this made me use SingleChildScrollView instead of ListView.
Tried this, this fixes the keyboard obscuring the bottom Rows by shifting them up, however now when clicking on the first Rows, they get moved out of view.
Tried KeyboardAvoider, but as this is not an onscreen Keyboard, I doesn't work.
You'll find a full minimal reproducible example here below.
main.dart
(Main + ChangeNotifierProvider for the state)
import 'package:flutter/material.dart';
import 'package:scrollTest/budgetPage.dart';
import 'package:scrollTest/budgetPageState.dart';
import 'package:provider/provider.dart';
void main() {
runApp(HomeScreen());
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ChangeNotifierProvider(
create: (_) => BudgetPageState(), child: BudgetPage()),
),
);
}
}
budgetPage.dart
(Main Page with the CustomList() and the buttonDial (custom keyboard, here just a simple container)
import 'package:flutter/material.dart';
import 'package:scrollTest/budgetPageState.dart';
import 'package:scrollTest/customList.dart';
import 'package:provider/provider.dart';
class BudgetPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
BudgetPageState budgetPageState = Provider.of<BudgetPageState>(context, listen: true);
ButtonDial buttonDial =
budgetPageState.showButtonDial ? ButtonDial() : null;
return Scaffold(
appBar: AppBar(
title: Text("Budget Page"),
),
body: Column(children: <Widget>[
Expanded(child: CustomList()),
if (buttonDial != null) buttonDial
]));
}
}
class ButtonDial extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height * 0.3,
child: Container(
color: Colors.blue,
),
);
}
}
customList.dart
(Simple List view SingleChildScrollView and a ScrollController)
import 'package:flutter/material.dart';
import 'package:scrollTest/CustomRow.dart';
class CustomList extends StatefulWidget {
#override
_CustomListState createState() => _CustomListState();
}
class _CustomListState extends State<CustomList> {
ScrollController _scrollController;
#override
void initState() {
super.initState();
_scrollController = ScrollController();
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scrollbar(
isAlwaysShown: true,
controller: _scrollController,
child: SingleChildScrollView(
controller: _scrollController,
child: Column(
children: _buildList(),
),
),
);
}
}
List<Widget> _buildList() {
List<Widget> widgetList = [];
for (int i = 0; i < 15; i++) {
widgetList.add(CustomRow(rowID: i));
}
return widgetList;
}
customRow.dart
(This is where I scroll to the selected row in handleOnTap)
import 'package:flutter/material.dart';
import 'package:scrollTest/budgetPageState.dart';
import 'package:provider/provider.dart';
class CustomRow extends StatefulWidget {
final int rowID;
CustomRow({Key key, #required this.rowID}) : super(key: key);
#override
_CustomRowState createState() => _CustomRowState();
}
class _CustomRowState extends State<CustomRow> {
BudgetPageState budgetPageState;
void handleOnTap(BuildContext context) {
if (!budgetPageState.isSelected(widget.rowID)) {
Scrollable.ensureVisible(context, duration: Duration(milliseconds: 200), alignment: 0.5);
}
budgetPageState.toggleButtonDial(widget.rowID);
budgetPageState.updateIsSelected(widget.rowID);
}
#override
void initState() {
super.initState();
budgetPageState = Provider.of<BudgetPageState>(context, listen: false);
budgetPageState.insertRowInHashMap(widget.rowID);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap:() => handleOnTap(context),
child: Container(
height: 60,
width: double.infinity,
color: budgetPageState.isSelected(widget.rowID)
? Colors.grey[200]
: Colors.white,
child: Center(
child: Text(
"Test ${widget.rowID}",
),
),
),
);
}
}
budgetPageState.dart
(The state managed using ChangeNotifier. Mainly contains logic for selecting/deselecting a row as well as logic for when to show the keyboard (using bool showButtonDial and notifyListeners())
import 'dart:collection';
import 'package:flutter/material.dart';
class BudgetPageState extends ChangeNotifier {
bool showButtonDial = false;
Map<int, bool> _isSelectedMap = HashMap();
int selectedId = -1;
bool isSelected(int rowId) {
return this._isSelectedMap[rowId];
}
Map<int, bool> get isSelectedMap => _isSelectedMap;
void updateIsSelected(int rowId) async {
///Select the row [rowId] if we tapped on a different one than the one
///that is currently highlighted ([selectedId])
///The same row was tapped, we remove the highlight i.e. we don't
///put it back to [true]
//Unselect all
_isSelectedMap.forEach((k, v) => _isSelectedMap[k] = false);
if (selectedId != rowId) {
this._isSelectedMap[rowId] = true;
selectedId = rowId;
} else {
selectedId = -1;
}
notifyListeners();
}
void toggleButtonDial(int rowId) {
if (!showButtonDial) {
showButtonDial = true;
} else if (rowId == selectedId) {
showButtonDial = false;
}
}
void insertRowInHashMap(int subcatId) {
this._isSelectedMap[subcatId] = false;
}
}
In my code below, I am struggling with LifeCyrles in Flutter where I can update my State in Provider, APPARENTLY, only in didChangeDependencies hook or in a template widget (via events hung up on buttons or so).
Alright, I don't mind that only didChangeDependencies hook works for me BUT when my logic in earlier mentioned hook depends on some class properties I am having problems with the accuracy of the class data.
I get data one step behind (since it's called before build hook I guess).
I cannot run this logic in the build hook because it includes a request to change the state in Provider. If I try to change the state there I've got either this error:
setState() or markNeedsBuild() called during build.
or this one
The setter 'lastPage=' was called on null.
Receiver: null
Tried calling: lastPage=true
What I want to do: I've got a wrapper widget which holds three other widgets: footer, header and pageViewer.
When I reach the last page I need to notify my wrapper widget about that so it reacts accordingly and hides header and footer.
I would appreciate any help here!
The focused code:
Here is the problem and must be solution
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:ui_flutter/screens/welcome/welcome_bloc.dart';
import 'package:flutter/scheduler.dart';
class _FooterState extends State<Footer> {
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
final WelcomeBloc _welcome = Provider.of<WelcomeBloc>(context);
_welcomeBloc = _welcome;
// this._detectLastPage();
}
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.bottomCenter,
padding: EdgeInsets.symmetric(vertical: 30.0, horizontal: 30.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
this.stepper,
this.nextArrow,
],
),
);
}
_detectLastPage() {
// Here I've got inaccurate data
print(this.widget.currentStep);
}
}
I have already tried some other hooks like Scheduler but maybe I did something wrong there.
SchedulerBinding.instance
.addPostFrameCallback((_) => this._detectLastPage());
It's called only once at the first build-up round and that's it.
I lack an Angular hook here AfterViewInit. It would be handy here.
or Mounted in VueJS
That's the rest of my code if you'd like to see the whole picture.
If you have any suggestions on the architecture, structure or something else you are welcome. It's highly appreciated since I'm new to Flutter.
main.dart
import 'package:flutter/material.dart';
import 'package:ui_flutter/routing.dart';
import 'package:provider/provider.dart';
import 'screens/welcome/welcome_bloc.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => WelcomeBloc()),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/welcome',
onGenerateRoute: RouteGenerator.generateRoute,
),
);
}
}
welcome.dart (my wrapper)
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
import 'package:ui_flutter/screens/welcome/welcome_bloc.dart';
import './footer.dart';
import './viewWrapper.dart';
import './header.dart';
// import 'package:ui_flutter/routing.dart';
class Welcome extends StatefulWidget {
#override
_WelcomeState createState() => _WelcomeState();
}
class _WelcomeState extends State<Welcome> {
WelcomeBloc _welcomeBloc;
#override
Widget build(BuildContext context) {
final WelcomeBloc _welcome = Provider.of<WelcomeBloc>(context);
this._welcomeBloc = _welcome;
print('Welcome: _welcome.currentPage - ${this._welcomeBloc.lastPage}');
return Scaffold(
body: SafeArea(
child: Stack(
children: <Widget>[
ViewerWrapper(),
Footer(
currentStep: _welcomeBloc.currentPage,
totalSteps: 3,
activeColor: Colors.grey[800],
inactiveColor: Colors.grey[100],
),
WelcomeHeader,
],
),
),
);
}
}
welcomeBloc.dart (my state via Provider)
import 'package:flutter/material.dart';
class WelcomeBloc extends ChangeNotifier {
PageController _controller = PageController();
int _currentPage;
bool _lastPage = false;
bool get lastPage => _lastPage;
set lastPage(bool value) {
_lastPage = value;
notifyListeners();
}
int get currentPage => _currentPage;
set currentPage(int value) {
_currentPage = value;
notifyListeners();
}
get controller => _controller;
nextPage(Duration duration, Curves curve) {
controller.nextPage(duration: duration, curve: curve);
}
}
footer.dart (that's where I've problems with data at the very bottom of the code - _detectLastPage method)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:ui_flutter/screens/welcome/welcome_bloc.dart';
import 'package:flutter/scheduler.dart';
class Footer extends StatefulWidget {
final int currentStep;
final int totalSteps;
final Color activeColor;
final Color inactiveColor;
final Duration duration;
final Function onFinal;
final Function onStart;
Footer({
this.activeColor,
this.inactiveColor,
this.currentStep,
this.totalSteps,
this.duration,
this.onFinal,
this.onStart,
}) {}
#override
_FooterState createState() => _FooterState();
}
class _FooterState extends State<Footer> {
final double radius = 10.0;
final double distance = 4.0;
Container stepper;
Container nextArrow;
bool lastPage;
WelcomeBloc _welcomeBloc;
#override
void didChangeDependencies() {
super.didChangeDependencies();
final WelcomeBloc _welcome = Provider.of<WelcomeBloc>(context);
_welcomeBloc = _welcome;
this._detectLastPage();
}
#override
Widget build(BuildContext context) {
this._makeStepper();
this._makeNextArrow();
return Container(
alignment: Alignment.bottomCenter,
padding: EdgeInsets.symmetric(vertical: 30.0, horizontal: 30.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
this.stepper,
this.nextArrow,
],
),
);
}
_makeCirle(activeColor, inactiveColor, position, currentStep) {
currentStep = currentStep == null ? 0 : currentStep - 1;
Color color = (position == currentStep) ? activeColor : inactiveColor;
return Container(
height: this.radius,
width: this.radius,
margin: EdgeInsets.only(left: this.distance, right: this.distance),
decoration: BoxDecoration(
color: color,
border: Border.all(color: activeColor, width: 2.0),
borderRadius: BorderRadius.circular(50.0)),
);
}
_makeStepper() {
List<Container> circles = List();
for (var i = 0; i < widget.totalSteps; i++) {
circles.add(
_makeCirle(this.widget.activeColor, this.widget.inactiveColor, i,
this.widget.currentStep),
);
}
this.stepper = Container(
child: Row(
children: circles,
),
);
}
_makeNextArrow() {
this.nextArrow = Container(
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: GestureDetector(
onTap: () {
_welcomeBloc.controller.nextPage(
duration: this.widget.duration ?? Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
},
child: Icon(
Icons.arrow_forward,
)),
),
);
}
_onLastPage() {
if (this.widget.onFinal != null) {
this.widget.onFinal();
}
}
_onFirstPage() {
if (this.widget.onStart != null) {
this.widget.onStart();
}
}
_detectLastPage() {
// Here I've got inaccurate data
int currentPage =
this.widget.currentStep == null ? 1 : this.widget.currentStep;
if (currentPage == 1 && this.widget.currentStep == null) {
this._onFirstPage();
} else if (currentPage == this.widget.totalSteps) {
print('lastPage detected');
setState(() {
this.lastPage = true;
});
_welcomeBloc.lastPage = true;
this._onLastPage();
} else {
setState(() {
this.lastPage = false;
});
_welcomeBloc.lastPage = false;
}
}
}
Thanks in advance!
I am new to flutter as well, But I have learned about a few architecture patterns that have helped me build some apps.
Here is how I do it:
Create a Provider which holds the data for you in runtime. (It can be a Bloc in your case). Stick to one architecture, don't try to put providers and blocs in the same project. Both are used for state management and only using one would be a great practice.
Second, Register the providers using ChangeNotificationProvider or any other widgets which does a similar job of rebuilding the child widget when a data gets changed.
Third, Get the provider in the build method of the widget that is supposed to change when the value of the variable provider changes. This way only the concerned widget is redrawn.
For your case,
If you want to hide the header and footer once you reach the last page, you can declare a variable, let's say isLastPage set to false by default in your provider.
Next, wrap the widget, i.e. header and footer with ChangeNotificationListner
Now, let that widget decide what it has to do based on the value of isLastPage, either hide itself or show.
I hope this helps!
At the long run, I seem to have found Mounted lifecycle hook in Flutter which is implemented with the help of Future.microtask. Unlike .addPostFrameCallback:
SchedulerBinding.instance
.addPostFrameCallback((_) => this._detectLastPage());
Which is triggered only once like InitState (but only at the end of the build execution), Future.microtask can be placed inside build block and be invoked after every change and state update.
It doesn't solve the problem with the inaccurate state in didChangeDependencies hook but provides another way to perform post-build executions.
Credits for the current solution to #Abion47
example
Future.microtask(() => this._detectLastPage());
I'm trying to change the active page index via a pagecontroller in Flutter, using the Bloc pattern and its throwing "'_positions.isNotEmpty': ScrollController not attached to any scroll views.".
This is my code:
WelcomeWizardScreen:
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertest/blocs/wizard/bloc/bloc.dart';
import 'package:fluttertest/screens/wizardsteps/joincongregation.dart';
import 'package:fluttertest/screens/wizardsteps/welcometomapman.dart';
class WelcomeWizardScreen extends StatefulWidget {
#override
_WelcomeWizardScreenState createState() => _WelcomeWizardScreenState();
}
class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
final WizardBloc wizardBloc = WizardBloc();
#override
Widget build(BuildContext context) {
// TODO: implement build
return BlocProvider(
builder: (BuildContext context) => WizardBloc(),
child: PageView(
children: <Widget>[WelcomeToMapMan(), JoinCongregation()],
controller: wizardBloc.pageController,
),
);
}
}
WizardBloc:
import 'package:bloc/bloc.dart';
import 'package:flutter/widgets.dart';
import 'wizard_state.dart';
import 'wizard_event.dart';
class WizardBloc extends Bloc<WizardEvent, WizardState> {
int activeStep = 0;
final PageController pageController = PageController(initialPage: 0, keepPage: false, viewportFraction: 0.4);
#override
WizardState get initialState => WelcomeToMapManState();
#override
Stream<WizardState> mapEventToState(
WizardEvent event,
) async* {
if (event is ChangePage)
{
pageController.jumpToPage(event.pageIndex);
}
// TODO: Add Logic
}
Stream<WizardState> _mapJoinCongregationToState() async* {
}
}
One of the screens in the PageView:
class JoinCongregation extends StatelessWidget {
#override
Widget build(BuildContext context) {
final WizardBloc _wizardBloc = BlocProvider.of<WizardBloc>(context);
// TODO: implement build
return Column(
children: <Widget>[
Center(
child: Text("this is step 2"),
),
RaisedButton(
child: Text("back to step 1"),
onPressed: () => {_wizardBloc.dispatch(ChangePage(0))},
)
],
);
}
}
It seems like the PageViewController isn't "attached" to the PageView when it is called on to change pages, but it initalises correctly (on the correct page index).
How can I solve this? I'm fairly new to flutter.
You shouldn't create a PageController in a bloc because a bloc should not be coupled with the UI (theoretically you should be able to reuse your bloc between Flutter and AngularDart). Please refer to https://github.com/felangel/bloc/issues/18 for an example of how you can accomplish this.