I have a widget page which lets me edit the source of an iframe. This works fine, but when I leave the page and return, my iframe becomes stuck in the state that it was when I initially left the page and then cant be edited.
Although the iframes src is changing on in the flutter code, it doesn't actually render the new src, again only rendering the old one. I've been stuck trying to fix this for a while and have no clue why its not working, so help is very appreciated. Thanks.
final IFrameElement _iframeElement = IFrameElement();
#override
void initState() {
super.initState();
print('INITIALAISING');
widthValueController.addListener(_setValueWidth);
heightValueController.addListener(_setValueHeight);
data = 'CarbTreePlas';
_iframeElement.src =
'/assets/lib/company_widget/widget-site/widget.html?slug=${widget.slug}&layout=box&data=$data';
_iframeElement.attributes['frameBorder'] = '0';
_iframeElement.attributes['scrolling'] = 'no';
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(
'iframeElement',
(int viewId) => _iframeElement,
);
widgetCode =
'<iframe frameBorder = "0" Scrolling = "0" width="$currValueWidth" height="$currValueHeight" src=${_iframeElement.src} />';
}
Container(
color: Colors.white,
height: currValueHeight,
width: currValueWidth,
child: HtmlElementView(
key: UniqueKey(),
viewType: 'iframeElement',
),
),
Related
I am creating a class for notifications that is outside of context using bot_toast, which will be called on API errors and such. I need to leave a margin that is relative to the screen width, but I don't have the context here. I could pass the context around, it doesn't seem like the best option.
So I am ending up with something like this
Function CustomToast (message) {
return BotToast.showAttachedWidget(
attachedBuilder: (_) => Align(
alignment: Alignment.topRight,
child: Container(
margin: new EdgeInsets.only(top: 25, right: 25.0),
color: Colors.blue,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
message,
),
),
),
),
duration: Duration(seconds: 20),
target: Offset(520, 520));
}
I need to set the righ margin as a percentage. How would I access the screen width here? is it possible without context? Any suggestion is welcome, even a change of toast library
Perhaps an app level model. Once you have a context, you can set the screen resolution for later retrieval of calculated percentage. I use this approach frequently.
class ScreenInfoViewModel {
List<String> _setupCompleted = [];
String appName;
String packageName;
String version;
String buildNumber;
double screenWidth;
ScreenInfoViewModel() {
init();
}
init() async {
var packageInfo = await PackageInfo.fromPlatform();
appName = packageInfo.appName;
packageName = packageInfo.packageName;
version = packageInfo.version;
buildNumber = packageInfo.buildNumber;
}
bool _smallScreen = false;
bool _mediumScreen = false;
bool _largeScreen = false;
void setScreenSize(double this.screenWidth, double diagonalInches) {
_smallScreen = diagonalInches < 5.11;
_mediumScreen = !_smallScreen && diagonalInches <= 5.6;
_largeScreen = diagonalInches > 5.6;
}
bool get isSmallScreen => _smallScreen;
bool get isMediumScreen => _mediumScreen;
bool get isLargeScreen => _largeScreen;
String get screenSize {
String size = 'S';
if (_mediumScreen) size = 'M';
if (_largeScreen) size = 'L';
return size;
}
}
I use it this way:
class SetupScreenInfo extends HookWidget {
// Use GetIt package to retrieve the model
final ScreenInfoViewModel _s = locator();
final _scaffoldKey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
/// This is the first 'context' with a MediaQuery, therefore,
/// this is the first opportunity to set these values.
/// widthPx and diagonalInches are from sized_context package.
_s.setScreenSize(context.widthPx, context.diagonalInches);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (context == null) return;
Navigator.pushReplacementNamed(context, splashRoute);
});
return SafeArea(
child: Scaffold(
key: _scaffoldKey,
body: Material(color: Colors.yellow[300]),
),
);
}
}
Finally, SetupScreenInfo is my initial route from my MaterialApp.
This code is an edit of my production code and has not been tested and is not a runnable example.
Hope this help generate some thought.
I'm creating a mini-game in my app, which should consist of a grid of 3x3 buttons, which will flash randomly in sequence one-by-one and the user has to recreate it.
I managed to create the buttons with GridView() and set up a timer, but right now I'm struggling with changing the color property of the button inside the GridView(). Which got me thinking If I'm using GridView() correctly.
I want to change the color of a button multiple times via Timer in a random sequence and then the user should recreate it. Can I do it with a timer and GridView()or is there an easier way of doing this?
(My mini-game should be similar to Among us Reactor task)
import 'dart:async';
import 'package:flutter/material.dart';
class Challenge extends StatefulWidget {
#override
_ChallengeState createState() => _ChallengeState();
}
class _ChallengeState extends State<Challenge> {
Timer timer;
Map userData = {};
int indexSaved;
#override
void initState() {
super.initState();
timer = new Timer.periodic(new Duration(seconds: 2), (Timer timer) {
print('hey');
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
#override
Widget build(BuildContext context) {
userData = ModalRoute.of(context).settings.arguments;
print(userData);
return Scaffold(
body: SafeArea(
child: Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: GridView.count(
crossAxisCount: 3,
// mainAxisSpacing: 20.0,
shrinkWrap: true,
children: List.generate(9, (index) {
return Center(
child: MaterialButton(
color: Colors.blueGrey[300],
padding: EdgeInsets.all(50.0),
splashColor: Colors.white,
onPressed: () {
indexSaved = index;
print(indexSaved);
},
),
);
}),
),
),
),
),
);
}
}
EDIT 21/10/2020:
I created a little function that should generate sequences and I'm launching them inside the timer. I worked on the answers which I got and tried to redo them so I could use them in my use case.
The color I'm changing with:
color: indexWithColor == index
? Colors.indigo
: Colors.blueGrey[300],
which works great.
(The snippet was edited)
List<int> correctValues = [];
int indexWithColor;
int indexDisplayed = 0;
void generateNewLevel() {
final random = new Random(seed);
correctValues.add(random.nextInt(9));
timer = new Timer.periodic(new Duration(milliseconds: 500), (Timer timer) {
setState(() {
indexWithColor = correctValues[indexDisplayed];
// print('Index color ' + indexWithColor.toString());
indexDisplayed++;
if (indexDisplayed == correctValues.length) {
new Timer(new Duration(milliseconds: 500), () {
setState(() {
indexWithColor = null;
indexDisplayed = 0;
});
timer.cancel();
});
timer.cancel();
}
});
});
}
Right now I'm using a button to generate a new level (later I will change it when I solve this problem). It works, but I have issues with it.
Users cannot distinguish if the button is pressed twice (right now its color is pressed for a bit longer).
2. It seems like the buttons have a bit delay when they are launching. It's not exact 500ms and the sleep is really messy.
3. The first level (with a one-long sequence is not visible as it gets changed as soon as the timer is canceled.
Is there a better option?
EDIT: 21/10/2020 12:00pm
I solved the second and third problems with edited timer, so it kinda works right now, but isn't there a better way?
Check the edited snippet. -^
If you want to just randomly change the color of your button this is one way to do it:
(Edited: to make sure that same index isn't selected twice in a row)
final rng = Random();
int indexWithColor;
#override
void initState() {
super.initState();
timer = new Timer.periodic(new Duration(seconds: 2), (Timer timer) {
setState(() {
int tempIndex;
do {
tempIndex = rng.nextInt(9);
} while (tempIndex == indexWithColor);
indexWithColor = tempIndex;
});
});
}
and in your MaterialButton:
color: indexWithColor == index
? Colors.red
: Colors.blueGrey[300],
Yes, you can do it with a Timer. But you need to expand your list with a boolean field which is isIndexSaved.
class NewModel {
int index;
bool isIndexSaved;
}
Then you need to generate a new list with newModel and set isIndexSaved to true when onPressed(). And the color of the MaterialButton should look like:
color: newModel.isIndexSaved
? Colors.red[300], // desired color
: Colors.blueGrey[300],
I am using Flutter and the Bloc Pattern with rxdart and would like to have a debug mode in my app similar like you enable the developer mode in Android the user should touch 5 times a logo in the UI.
The UI part is pretty simple with:
Column _renderLogo(BuildContext context) => new Column(
children: <Widget>[
GestureDetector(
onTap: () => BlocProvider.of(context).debugEnabledSink.add(true),
child: Container( ...more logo rendering...
With that in mind, I am looking for an easy elegant way to enable the detection of 5 events in 10 seconds. The whole detection should reset when not enough events are detected in any 10 second time window.
You can use a pseudo-timer to achieve this:
const maxDebugTimerSeconds = 10;
const maxTapCount = 5;
DateTime firstTap;
int tapCount;
void doGestureOnTap() {
final now = DateTime.now();
if (firstTap != null && now.difference(firstTap).inSeconds < maxDebugTimerSeconds) {
tapCount++;
if (tapCount >= maxTapCount) {
BlocProvider.of(context).debugEnabledSink.add(true);
}
} else {
firstTap = now;
tapCount = 0;
}
}
...
GestureDetector(
onTap: doGestureOnTap,
...
),
This answer is based on the comment of #pskink above. I just post it here because it seems like a very elegant way of solving my problem. Thank you #pskink
DebugSwitcher might not be the best class Name it's more TimeWindowEventDetector but you get the idea.
class DebugSwitcher {
final Duration window;
final int cnt;
bool value = false;
var timeStamps = [];
DebugSwitcher({this.window = const Duration(seconds: 10), this.cnt = 5});
call(data, sink) {
var now = DateTime.now();
timeStamps.removeWhere((ts) => now.difference(ts) >= window);
timeStamps.add(now);
if (timeStamps.length >= cnt) {
timeStamps = [];
sink.add(value = !value);
}
}
}
Than u listen to the Sink Events this way:
_debugEnabledController.stream
.transform(StreamTransformer.fromHandlers(handleData: DebugSwitcher()))
.listen(
(_) {
_isDebugModeOn.value = true;
infoInternal.value = 'Enabled Debug Mode';
},
);
The Sink is defined like this:
final StreamController<bool> _debugEnabledController =
StreamController<bool>();
Sink<bool> get debugEnabledSink => _debugEnabledController.sink;
In the UI the code looks like this:
Column _renderLogo(BuildContext context) => new Column(
children: <Widget>[
GestureDetector(
onTap: () => BlocProvider.of(context).debugEnabledSink.add(true),
child: Container(
margin: const EdgeInsets.only(top: 10.0, right: 10.0),
height: 80.0,
child: Theme.of(context).primaryColorBrightness ==
Brightness.light
? new Image.asset('assets/app_icon_light.png', fit: BoxFit.contain)
: new Image.asset('assets/app_icon.png', fit: BoxFit.contain),
),
),
],
);
I've been learning Dart with flutter and was creating Weather App for practices , I managed to successfully create the UI , I used Dark Sky weather API since it has 7 day weather forecast but instead of taking the long road of decoding json and models , I used Dark Sky Library.
Here's the code for the API
Future<Null> getWeather() async {
Location location = Location();
await location.getCurrentPosition();
var darksky = DarkSkyWeather(
MY_API_KEY,
units: Units.SI,
language: Language.English,
);
var forecast = await darksky.getForecast(
location.latitude, location.longitude, excludes: [
Exclude.Hourly,
Exclude.Minutely,
Exclude.Alerts,
Exclude.Flags
]);
print(forecast.currently.temperature.round());
print(forecast.daily.data[0].temperatureMax);
}
I wanted to use the forecast variable outside the functions and to fill the text fields such the humidity temperature among other data in the package . How can I access it ? any help will be appreciated .
Thanks
Take a look at this code
class _MyHomePageState extends State<MyHomePage> {
String _data; // This holds the data to be shown
#override
void initState() {
_data = "Press the button"; // This is the initial data // Set it in initState because you are using a stateful widget
super.initState();
}
// Call this to set the new data
void setData() {
setState(() { // Remember to use setState() else the changes won't appear
_data = 'Hello World';
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Message',
),
Text(
'$_data', // See how the data is fed into this text widget
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: setData, // When I press the button, the new data is set
child: Icon(Icons.send),
),
);
}
}
Output:
Initially:
After pressing the button:
Do the same thing in your code.
// Your code modified a bit
// Make sure this is a stateful widget because you are changing values
double _tempNow; // Declare two vars inside the class
double _tempMax;
// Your initState
#override
void initState() {
_tempNow = 0; // Initial values
_tempMax = 0;
super.initState();
}
// Build method
#override
Widget build(BuildContext context) {
// Your layout Code.....
Text(
'Current: $_tempNow' // Here you set the tempNow
),
......
Text(
'Maximum: $_tempMax' // and tempMax in the text widgets
),
// End
}
Future<Null> getWeather() async {
Location location = Location();
await location.getCurrentPosition();
var darksky = DarkSkyWeather(
"8810b3633934b8c1975c51a2311dc1d0",
units: Units.SI,
language: Language.English,
);
var forecast = await darksky.getForecast(
location.latitude, location.longitude, excludes: [
Exclude.Hourly,
Exclude.Minutely,
Exclude.Alerts,
Exclude.Flags
]);
// Change the values here.
// After the data is fetched, The changes will be made automatically
setState(() {
_tempNow = forecast.currently.temperature.round();
_tempMax = forecast.daily.data[0].temperatureMax;
});
}
In quite a similar manner, you can use text fields too. In that case, You will need to use TextEditingController and set the text there but, it seems in your case, you need read-only text to show the information to the user so a simple Text widget is enough
my use case is to create a list view of articles (each item have the same look, there could be huge amount of articles, e.g. > 10000). I tried with
- ListView with ListView.builder: it supposes only to render the item when the item is displayed
- ScrollController: to determine when to load the next items (pagination)
- then I use List to store the data fetched from restful API using http, by adding the data from http to the List instance
this approach is OK, but in case the user keeps on scrolling pages, the List instance will have more and more items, it can crash with stack Overflow error.
If I don't call List.addAll(), instead I assign the data fetched from api, like: list = data;
I have problem that when the user scroll up, he/she won't be able to see the previous items.
Is there a good approach to solve this? Thanks!
import 'package:flutter/material.dart';
import 'package:app/model.dart';
import 'package:app/components/item.dart';
abstract class PostListPage extends StatefulWidget {
final String head;
DealListPage(this.head);
}
abstract class PostListPageState<T extends PostListPage> extends State<PostListPage> {
final int MAX_PAGE = 2;
DealListPageState(String head) {
this.head = head;
}
final ScrollController scrollController = new ScrollController();
void doInitialize() {
page = 0;
try {
list.clear();
fetchNextPage();
}
catch(e) {
print("Error: " + e.toString());
}
}
#override
void initState() {
super.initState();
this.fetchNextPage();
scrollController.addListener(() {
double maxScroll = scrollController.position.maxScrollExtent;
double currentScroll = scrollController.position.pixels;
double delta = 200.0; // or something else..
if ( maxScroll - currentScroll <= delta) {
fetchNextPage();
}
});
}
#override
void dispose() {
scrollController.dispose();
super.dispose();
}
void mergeNewResult(List<PostListItem> result) {
list.addAll(result);
}
Future fetchNextPage() async {
if (!isLoading && mounted) {
page++;
setState(() {
isLoading = true;
});
final List<PostListItem> result = await doFetchData(page);
setState(() {
if (result != null && result.length > 0) {
mergeNewResult(result);
} else {
//TODO show notification
}
isLoading = false;
});
}
}
Future doFetchData(final int page);
String head;
List<PostListItem> list = new List();
var isLoading = false;
int page = 0;
int pageSize = 20;
final int scrollThreshold = 10;
Widget buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isLoading ? 1.0 : 0.0,
child: new CircularProgressIndicator(),
),
),
);
}
#override
Widget build(BuildContext context) {
ListView listView = ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (BuildContext context, int index) {
if (index == list.length) {
return buildProgressIndicator();
}
if (index > 0) {
return Column(
children: [Divider(), PostListItem(list[index])]
);
}
return PostListItem(list[index]);
},
controller: scrollController,
itemCount: list.length
);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(head),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () {
},
),
// action button
IconButton(
icon: Icon(Icons.more_horiz),
onPressed: () {
},
),
]
),
body: new RefreshIndicator(
onRefresh: handleRefresh,
child: listView
),
);
}
Future<Null> handleRefresh() async {
doInitialize();
return null;
}
}
in my case, when the list length is 600, I start to get stack overflow error like:
I/flutter ( 8842): Another exception was thrown: Stack Overflow
I/flutter ( 8842): Another exception was thrown: Stack Overflow
screen:
enter image description here
somehow flutter doesn't show any more details of the error.
I wrote some sample code for a related question about paginated scrolling, which you could check out.
I didn't implement cache invalidation there, but it would easily be extendable using something like the following in the getPodcast method to remove all items that are more than 100 indexes away from the current location:
for (key in _cache.keys) {
if (abs(key - index) > 100) {
_cache.remove(key);
}
}
An even more sophisticated implementation could take into consideration the scroll velocity and past user behavior to lay out a probability curve (or a simpler Gaussian curve) to fetch content more intelligently.