How to make a Flutter slider go exponential? - flutter

I want to use a (Cupertino)slider with which you can pick a number between 1-1000000. For every milestone, I will display another text. (1-10, 10-100, 100-1000, etc.) Since in the beginning, the steps are shorter, I want the slider not being that sensitive, and in the end, I wouldn't care it going with 100 each step, for example. Like it should go exponential. Does anybody have an idea of how to achieve that?
Thanks a lot!

I found a solution using https://pub.dev/packages/flutter_xlider
Thanks a lot!

Use the CupertinoSlider and some class that holds the min and max value, in this example I use RangeValues (it's a flutter class for another type of Slider, but I can use it to save a start and end value). When the onChangedEnd value is equal to the slider value then it displays a snackbar showing the milestone and update the range value from the old one 10 times. division parameter can be used to make a discrete slider, but if the max value is too big you can omit it or pass a null value to make it continuos
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
RangeValues range = RangeValues(1, 10);
double slide = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Builder( //Needed to find Scaffold.of(context) and display the snackbar
builder: (BuildContext context) {
return CupertinoSlider(
value: slide,
onChanged: (newValue) => setState(() => slide = newValue),
min: range.start,
max: range.end,
divisions: (range.end - range.start).toInt(),
onChangeEnd: (value) {
if (value == range.end) {
Scaffold.of(context)
..hideCurrentSnackBar()
..showSnackBar(SnackBar(
content: Text('New Milestone reached: ${slide.toInt().toString()}')));
setState(() => range = RangeValues(range.start * 10, range.end * 10));
}
},
);
}
),
Text(slide.toInt().toString())
]
)
);
}
}

Related

Global key is not restoring state sometimes of child widget

Basically, in this example, I am using global key to restore value of by child,
This work,
But sometimes, it appears that the global key does not restore the value of the child items.
To make it clear, I have added a checkbox widget and a slider widget to the first and last pages of this code (green and red).
When I change the slider value on the first page and try to see it on the third page, it works fine.
Same thing if I change checkbox value (like true) on first page and then click on third page it shows mostly true, but sometimes does not.
I am unable to understand the reason behind this issue. Please refer to the following gif to better understand my question.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatelessWidget {
final _key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Global Key Example")),
body: PageView.builder(
itemCount: 3,
itemBuilder: (context, index) {
switch (index) {
case 0:
return Container(
color: Colors.green,
child: ActionPage(_key),
);
break;
case 1:
return Container(
color: Colors.blue,
child: Text("Blank Page"),
);
break;
case 2:
return Container(
color: Colors.red,
child: ActionPage(_key),
);
break;
default:
throw "Not found";
}
},
),
);
}
}
class ActionPage extends StatefulWidget {
#override
_ActionPageState createState() => _ActionPageState();
ActionPage(key) : super(key: key);
}
class _ActionPageState extends State<ActionPage> {
bool _switchValue = false;
double _sliderValue = 0.5;
#override
Widget build(BuildContext context) {
return Column(
children: [
Switch(
value: _switchValue,
onChanged: (v) {
setState(() => _switchValue = v);
},
),
Slider(
value: _sliderValue,
onChanged: (v) {
setState(() => _sliderValue = v);
},
)
],
);
}
}
I'd recommened you to not use a GlobalKey here. Make sure to pass the needed data not the widget sate. If you take a look at the documentation the following pitfalls are listed:
GlobalKeys should not be re-created on every build. They should
usually be long-lived objects owned by a State object, for example.
Creating a new GlobalKey on every build will throw away the state of
the subtree associated with the old key and create a new fresh subtree
for the new key. Besides harming performance, this can also cause
unexpected behavior in widgets in the subtree. For example, a
GestureDetector in the subtree will be unable to track ongoing
gestures since it will be recreated on each build.
Instead, a good practice is to let a State object own the GlobalKey,
and instantiate it outside the build method, such as in
State.initState.
So you have to make sure that two widgets with the same key are never rendered on the screen at the same time. With a PageView this obviously can happen since you can display (eventhough there might be a transition state) two of your widgets with the same key at the same time. This can mess things up. Also they changed quite a bit in the past implementation wise if I recall correctly.
You should either pass the value directly to your widget or use something like an InheritedWidget to be safe.
Generally speaking I'd always try to avoid using GlobalKeys because they are quite complex and can have side effects which might not be always fully comprehensible. Also there are good alternatives.

How to set a variable value in a widget and in another widget?

I'm trying to set a variable value (number, in the code below) that exists in FirstWidget, from SecondWidget. And notify both widgets so the number will be updated in the two widgets.
class FirstWidget extends StatefulWidget {
#override
_FirstWidgetState createState() => _FirstWidgetState();
}
class _FirstWidgetState extends State<FirstWidget> {
int number = 0;
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'$number',
),
SecondWidget(),
Text(
'$number',
)
],
);
}
}
class SecondWidget extends StatefulWidget {
#override
_SecondWidgetState createState() => _SecondWidgetState();
}
class _SecondWidgetState extends State<SecondWidget> {
#override
Widget build(BuildContext context) {
return TextButton(
child: Text('The number is $number. Press to increase the number'),
onPressed: () {
setState(() {
number++;
});
},
);
}
}
(I know that that code gives an error, but the main idea was to give you the problem I want to solve).
The output I want it to be shown:
before pressing the button -
0
The number is 0. Press to increase the number
0
after pressing the button -
1
The number is 1. Press to increase the number
1
So I would be happy if you can help solving this.
Thanks.
There are many approaches to get the result you are looking for, this one is using ValueNotifier in order to change the value of number
Here is an example based on your code:
https://dartpad.dev/b6409e10de32b280b8938aa75364fa7b
We can use another State Management like Provider or Cubit, and we will get the same result.
Another way is to pass a function as a param in the second widget and execute that function when button is pressed

I was told that 25th line of code contains an issue where setState() with random color being used. Help identify an issue

Original task sounded like:
The application should: display the text "Hey there" in the middle of
the screen and after tapping anywhere on the screen a background color
should be changed to a random color. You can also add any other
feature to the app - that adds bonus points Please do not use any
external libraries for color generation
My solution (GitHub):
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: RandomBackgroundColorWidget(),
);
}
}
class RandomBackgroundColorWidget extends StatefulWidget {
#override
_RandomBackgroundColorWidget createState() => _RandomBackgroundColorWidget();
}
class _RandomBackgroundColorWidget extends State<RandomBackgroundColorWidget> {
int _colorIndex = 0xFF42A5F5;
void _randomColorIndexGenerator() {
final _rng = new Random();
setState(() => {_colorIndex = (_rng.nextInt(0xFFFFFF) + 0xFF000000)});
}
#override
Widget build(BuildContext context) {
return Stack(children: [
Material(
color: Color(_colorIndex),
child: Center(
child: Text("Hey there"),
),
),
GestureDetector(
onTap: () => _randomColorIndexGenerator(),
),
]);
}
}
While reviewing my test task interviewer said that 25th line of code contains an issue.
setState(() => {_colorIndex = (_rng.nextInt(0xFFFFFF) + 0xFF000000)});
And he commented:
"It is working in a way that is not intended by you."
Help to identify an issue in 25th line of code.
You are accidentally combining the two ways to declare a function in Dart: the arrow operator => and curly braces {}.
Line 25 should be:
setState(() => _colorIndex = _rng.nextInt(0xFFFFFF) + 0xFF000000);
with no extra curly braces.
The issue is a syntax error. When using setState() =>, you dont need the {}
setState(() {_colorIndex = (_rng.nextInt(0xFFFFFF) + 0xFF000000)});
or
setState(() => _colorIndex = (_rng.nextInt(0xFFFFFF) + 0xFF000000));
I couldn't find the error you mention, however I recommend that you always use init state when assigning default values.
Here other way
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: RandomBackgroundColorWidget(),
);
}
}
class RandomBackgroundColorWidget extends StatefulWidget {
#override
_RandomBackgroundColorWidget createState() => _RandomBackgroundColorWidget();
}
class _RandomBackgroundColorWidget extends State<RandomBackgroundColorWidget> {
Color _color;
#override
void initState() {
_color = Colors.white;
super.initState();
}
void getrandomColor() {
setState(() {
_color = Colors.primaries[Random().nextInt(Colors.primaries.length)];
});
}
#override
Widget build(BuildContext context) {
return Stack(children: [
Material(
color: _color,
child: Center(
child: Text("Hey there"),
),
),
GestureDetector(
onTap: getrandomColor,
),
]);
}
}
Issue is in nextInt function.
Flutter accepts color codes for generation up to 0xFFFFFFFF, where first pair of numbers is needed for opacity level and other pairs needed for RGB levels
nextInt function generates random number in range up to, but not including, passed number. For example, nextInt(3) will generate randomly 0,1 or 2, but not 3.
So original app was generating all random colors (from 0x0 to 0xFFFFFE) except last one - 0xFFFFFF
Therefore, 25th line should look like this in order to generate every possible color.
setState(() => _colorIndex = (_rng.nextInt(0x1000000) + 0xFF000000));

List.remove() removes the correct object but the object's state in the list shifts as if last entry is being deleted

So I have been trying to solve this issue for a couple of days now and I seem to have hit a real dead end here.
The issue (which I have simplified in the code below) is that when I try to remove an item from a List<"SomeDataObject">, it does actually remove the correct object from the list. This is evident because the ID that I arbitrarily assigned to the objects does shift just as I would expect it to when I remove something. However, oddly enough, even though the IDs shift in the list and the correct ID is removed, the states of all the widgets seem to act as though the last item is always removed, even when the deleted object is at the middle or even beginning of the list.
An example would be if the list looked as such:
Data(id: 630) // This one starts blue
Data(id: 243) // Let's say the user made this one red
Data(id: 944) // Also blue
And let's say I tried to remove the middle item from this list. Well what happens is that the list will look like this now:
Data(id: 630)
Data(id: 944)
This seems at first to be great because it looks like exactly what I wanted, but for some reason, the colors did not change their order and are still Red first, then Blue. The color state data seems to function independently from the actual objects and I could not find a clear solution.
I have code of an example program to reproduce this problem and I also have some pictures below of the program so that it is clearer what the issue is.
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: HomeScreen(),
);
}
}
// Home screen class to display app
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
List<DataWidget> widgetsList = List<DataWidget>();
// Adds a new data object to the widgets list
void addData() {
setState(() {
widgetsList.add(DataWidget(removeData));
});
}
// Removes a given widget from the widgets list
void removeData(DataWidget toRemove) {
setState(() {
widgetsList = List.from(widgetsList)..remove(toRemove);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
/* I used a SingleChildScrollView instead of ListView because ListView
resets an objects state when it gets out of view so I wrapped the whole
list in a column and then passed that to the SingleChildScrollView to
force it to stay in memory. It is worth noting that this problem still
exists even when using a regular ListView. */
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
// Added a SizedBox just to make column take up whole screen width
children: [...widgetsList, SizedBox(width: double.infinity)],
),
),
// FloatingActionButton just to run addData function for example
floatingActionButton: FloatingActionButton(onPressed: addData),
);
}
}
// Simple class that is a red square that when clicked turns blue when pressed
class DataWidget extends StatefulWidget {
DataWidget(this.onDoubleTap);
final Function(DataWidget) onDoubleTap;
// Just a random number to keep track of which widget this is
final String id = Random.secure().nextInt(1000).toString();
#override
String toStringShort() {
return id;
}
#override
_DataWidgetState createState() => _DataWidgetState();
}
class _DataWidgetState extends State<DataWidget> {
/* Here the example state information is just a color, but in the full version
of this problem this actually has multiple fields */
Color color = Colors.red;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
// onDoubleTap this passes itself to the delete method of the parent
onDoubleTap: () => widget.onDoubleTap(widget),
// Changes color on tap to whatever color it isn't
onTap: () {
setState(() {
color = color == Colors.red ? Colors.blue : Colors.red;
});
},
child: Container(
child: Center(child: Text(widget.id)),
width: 200,
height: 200,
color: color,
),
),
);
}
}
Before user changes any colors (object ID is displayed as text on container):
User changes middle container to blue by tapping:
User attempts to delete middle (blue) container by double tapping:
As you can see from the image above, the ID was deleted and the ID from below was moved up on the screen, but the color information from the state did not get deleted.
Any ideas for things to try would be greatly appreciated.
You need to make a few changes to your DataWidget class to make it work:
// Simple class that is a red square that when clicked turns blue when pressed
class DataWidget extends StatefulWidget {
Color color = Colors.red; // moved from _DataWidgetState class
DataWidget(this.onDoubleTap);
final Function(DataWidget) onDoubleTap;
// Just a random number to keep track of which widget this is
final String id = Random.secure().nextInt(1000).toString();
#override
String toStringShort() {
return id;
}
#override
_DataWidgetState createState() => _DataWidgetState();
}
class _DataWidgetState extends State<DataWidget> {
/* Here the example state information is just a color, but in the full version
of this problem this actually has multiple fields */
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
// onDoubleTap this passes itself to the delete method of the parent
onDoubleTap: () => widget.onDoubleTap(widget),
// Changes color on tap to whatever color it isn't
onTap: () {
setState(() {
widget.color =
widget.color == Colors.red ? Colors.blue : Colors.red;
});
},
child: Container(
child: Center(child: Text(widget.id)),
width: 200,
height: 200,
color: widget.color,
),
),
);
}
}

How Can I PAUSE or RESUME a async task using a button in flutter?

I'm Building An Flutter Application which requires image changes after a period of time. I thought using while loop with a sleep method inside may solve the problem. But It didn't, Image is only getting change after the loop ends. Application UI also gets froze.
So, I used the async Task which I can't control with a Button.
Desired Output: Image should be changed after every 10 seconds and the user can pause or resume method execution.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Test(
),
),
)
);
}}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
int imgnumber=1;
int varToCheckButtonPress = 0;
String BtnTxt = "START";
void inc(){
while(imgnumber<10)
{
print(imgnumber);
await Future.delayed(const Duration(seconds: 10));
setState(() {
imgnumber++;
});
}
}
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(flex: 1,
child: Container(
child: Image.asset('images/'+imgnumber.toString()+'.png'),
height: 500,
width:500,
color: Colors.green,
),
),
FlatButton(
child: Text(BtnTxt),
onPressed: (){
if (varToCheckButtonPress == 0) {
setState(() {
inc();
BtnTxt = 'PAUSE';
varToCheckButtonPress = 1;
});
} else if (varToCheckButtonPress == 1) {
setState(() {
BtnTxt = 'RESUME';
varToCheckButtonPress = 0;
});
}
},
)
],
);
}
}
I want the user to control the UI with a single button behave as START, PAUSE and RESUME.
Can we Use normal function To implement this functionality?
You should make use of Bloc pattern to manage your states, e.g: StreamBuilder, Providers, and make a timer to push new imageUrl to the sink and let the streamBuilder receive the latest imageUrl.
As for your button, all it controls is the timer. When u hit the play button, new imageUrl will keep pushing to the sink, while you press paused, simply stop the timer, and new image Url will not be pushing new imageUrl to the sink, and of course, reset the timer when you hit the stop button.
Here is a very detail Bloc pattern tutorial you can follow: Medium
The shortcut to achieve this is :
You can probably hold a function in async loop and call setState method on tap to change it's state.
For example :
call this function in desired location
while (_isPaused) {
await Future.delayed(Duration(milliseconds: 500));
}
and then call set state method from onTap, just like this
onTap:(){
setState((){
_isPaused? _isPaused=false: _isPaused=true;
});
}