So the current language I am using is flutter, although I believe this applies to any programming language. I am creating an app where I have a requirement to have an implemented custom keypad. The keypad has three configurations for three different text fields used in pipette.dart, I added part of the code for ASPIRATE SPEED and DISPENSE SPEED, althogh the same logic applies to volume shown in the image below.
When each text field is pressed the current highlight is switched to the container where the text field is ( The blue highlight below the text field). The keypad is also switched to control the text field which is pressed, shown in toggleKeyPad(). So for example when you click Aspirate Speed, isAspirate is true and isAspirateText is true, isAspirate is then passed to blueHighlightSpeed() (shown at the bottom of pipette.dart), which then highlights the container. when IsAspirateText is true it sets the keypad to use aspirateController which is the text editing controller for the text field in Aspirate Speed, the same logic applies to Dispense speed and Volume. Hopefully this helps anyone understand what my code is doing, I will update if I missed anything.
The question that I have is I would like to know if there is a way to minimize the amount of Boolean statements that I am using. Currently every time I click a new text field I have to change 8 different Booleans which can become very tedious and for other programmers will probably make the code harder to read and understand. I have only been programming for a few years now and would just like to know if there is a way to create this same logic without as many Booleans.
Is it maybe possible to use a switch statement here?
functions.dart
import 'package:flutter/material.dart';
import '../keyboard/text_key.dart';
import '../keyboard/textkey_functions.dart';
bool keypadUp = false;
bool isTextFieldShown = false;
bool isVolume = false;
bool isVolumeText = false;
bool isAspirate = false;
bool isAspirateText = true;
bool isDispense = false;
bool isDispenseText = false;
bool depthMemoryTop = false;
bool depthMemoryBottom = false;
TextEditingController aspirateController = TextEditingController();
TextEditingController dispenseController = TextEditingController();
TextEditingController volumeController = TextEditingController();
TextEditingController machController = TextEditingController();
Widget toggleKeyPad() {
if (isAspirateText == true) {
return KeyPad(
onTextInput: (myText) {
insertText(myText, aspirateController); //Accepts and inputs text into aspirateController
},
onBackspace: () {
backspace(aspirateController);
},
);
}
if (isVolumeText == true) {
return KeyPad(
onTextInput: (myText) {
insertText(myText, volumeController); //Accepts and inputs text into volumeController
},
onBackspace: () {
backspace(volumeController);
},
);
}
if (isDispenseText == true) {
return KeyPad(
onTextInput: (myText) {
insertText(myText, dispenseController); //Accepts and inputs text into dispenseController
},
onBackspace: () {
backspace(dispenseController);
},
);
} else {
return KeyPad(
onTextInput: (myText) {
insertText(myText, machController); // I didn't need the else statement so I created a fake controller which takes no input.
},
onBackspace: () {
backspace(machController);
},
);
}
}
Padding blueHighlightVolume (bool isSelect) {
if (isSelect == true) {
return Padding(
padding: const EdgeInsets.only(top: 190),
child: Container(
width: 140,
height: 10,
color: Color.fromRGBO(2, 58, 107, 1),
),
);
} else {
return Padding(
padding: const EdgeInsets.only(top: 190),
child: Container(
width: 140,
height: 10,
),
);
}
}
Padding blueHighlightSpeed (bool isSelect) {
if (isSelect == true) {
return Padding(
padding: const EdgeInsets.only(top: 90, left: 1),
child: Container(
width: 140,
height: 10,
color: Color.fromRGBO(2, 58, 107, 1),
),
);
} else {
return Padding(
padding: const EdgeInsets.only(top: 90, left: 1),
child: Container(
width: 140,
height: 10,
),
);
}
}
Padding blueHighlightDepthTop (bool isSelect) {
if (isSelect == true) {
return Padding(
padding: const EdgeInsets.only(top: 89, left: 15),
child: Container(
width: 350,
height: 10,
color: Color.fromRGBO(2, 58, 107, 1),
),
);
} else {
return Padding(
padding: const EdgeInsets.only(top: 90, left: 15),
child: Container(
width: 350,
height: 10,
),
);
}
}
Padding blueHighlightDepthBottom (bool isSelect) {
if (isSelect == true) {
return Padding(
padding: const EdgeInsets.only(top: 90, left: 15),
child: Container(
width: 350,
height: 10,
color: Color.fromRGBO(2, 58, 107, 1),
),
);
} else {
return Padding(
padding: const EdgeInsets.only(top: 90, left: 15),
child: Container(
width: 350,
height: 10,
),
);
}
}
part of pipette.dart
Column(
children: [
Stack(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'ASPIRATE SPEED',
style: TextStyle(
fontFamily: 'Droid Sans',
color: Color.fromRGBO(2, 58, 107, 1),
fontSize: 12.0,
),
),
),
Container(
decoration: BoxDecoration(
border: Border(
left: BorderSide(width: 1, color: Colors.grey),
),
),
width: 140,
height: 100,
//color: Colors.red,
),
Padding(
padding: const EdgeInsets.only(left: 20.0, top: 36.0),
child: Container(
width: 75,
height: 40,
child: TextField(
controller: aspirateController,
textAlign: TextAlign.center,
readOnly: true,
onTap: () {
setState(() {
/*
* set isAspirate and isAspirateText to true, highlights aspirate speed container and
* changes aspirateController to accept input from the keypad.
*
*/
isAspirateText = true;
isDispenseText = false;
isVolumeText = false;
isAspirate = true;
isDispense = false;
isVolume = false;
depthMemoryTop = false;
depthMemoryBottom = false;
print(isAspirate);
});
},
decoration: InputDecoration(
border: OutlineInputBorder(),
),
),
),
),
blueHighlightSpeed(isAspirate),
],
),
Stack(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'DISPENSE SPEED',
style: TextStyle(
fontFamily: 'Droid Sans',
color: Color.fromRGBO(2, 58, 107, 1),
fontSize: 12.0,
),
),
),
Container(
decoration: BoxDecoration(
border: Border(
left: BorderSide(width: 1, color: Colors.grey),
),
),
width: 140,
height: 100,
//color: Colors.red,
),
Padding(
padding: const EdgeInsets.only(left: 20.0, top: 36.0),
child: Container(
width: 75,
height: 40,
child: TextField(
controller: dispenseController,
textAlign: TextAlign.center,
readOnly: true,
onTap: () {
setState(() {
isVolumeText = false;
isAspirateText = false;
isDispenseText = true;
isAspirate = false;
isDispense = true;
isVolume = false;
depthMemoryTop = false;
depthMemoryBottom = false;
print(isAspirate);
});
},
decoration: InputDecoration(
border: OutlineInputBorder(),
),
),
),
),
blueHighlightSpeed(isDispense),
],
),
],
),
keypad.dart
class KeyPad extends StatelessWidget
{
KeyPad({
required this.onTextInput,
required this.onBackspace,
});
final double width = 275;
final double height = 90;
final ValueSetter<String> onTextInput;
final VoidCallback onBackspace;
void _textInputHandler(String text) => onTextInput.call(text);
void _backspaceHandler() => onBackspace.call();
Container buildRowOne()
{
return Container(
width: width,
height: height,
child: Row(
children: [
TextKey(
text: '7',
onTextInput: _textInputHandler,
),
TextKey(
text: '8',
onTextInput: _textInputHandler,
),
TextKey(
text: '9',
onTextInput: _textInputHandler,
),
],
),
);
}
Container buildRowTwo()
{
return Container(
width: width,
height: height,
child: Row(
children: [
TextKey(
text: '4',
onTextInput: _textInputHandler,
),
TextKey(
text: '5',
onTextInput: _textInputHandler,
),
TextKey(
text: '6',
onTextInput: _textInputHandler,
),
],
),
);
}
Container buildRowThree()
{
return Container(
width: width,
height: height,
child: Row(
children: [
TextKey(
text: '1',
onTextInput: _textInputHandler,
),
TextKey(
text: '2',
onTextInput: _textInputHandler,
),
TextKey(
text: '3',
onTextInput: _textInputHandler,
),
],
),
);
}
Container buildRowFour()
{
return Container(
width: width,
height: height,
child: Row(
children: [
BackspaceKey(
onBackspace: _backspaceHandler,
),
TextKey(
text: '0',
onTextInput: _textInputHandler,
),
TextKey(
text: '.',
onTextInput: _textInputHandler,
),
],
),
);
}
Padding buildRowFive()
{
return Padding(
padding: const EdgeInsets.only(top: 35),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Color.fromRGBO(92, 103, 148, 1)),
borderRadius: BorderRadius.circular(45.0),
),
width: 175,
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Image.asset('assets/text_key/done.png'),
onPressed: () {},
),
],
),
),
);
}
@override
Widget build(BuildContext context)
{
return SafeArea(
child: Container(
child: Column(
children: [
buildRowOne(),
buildRowTwo(),
buildRowThree(),
buildRowFour(),
buildRowFive(),
],
),
),
);
}
}
2
Answers
The best thing to use in this situation would be a state machine, which would be much easier to implement than trying to use enumerations or switch statements — which do not necessarily reduce the complexity of your code.
This video does a great job of explaining this:
https://www.youtube.com/watch?v=I1Mzx_tSpew
You don’t need to know much about computational theory to implement a basic (finite) state machine, but it’s a highly effective method for problems that can lead to infinite booleans.
Flutter also has access to a few good libraries for such a thing:
https://www.jawahar.tech/blog/finite-state-machine-flutter/ and
https://pub.dev/packages/statemachine
The first change I believe you should do is to get rid of the
isSomethingText
variables. TheisSomethingText
variables on your code are true ifisSomething
is true, which means that you could use only one of those variables for everything.The problem
Your code is quite complex, making it harder for me to understand your question, so I will try to simplify the situation a bit:
Let’s say we have an app that will display a color, and in order to do so, it asks the user to mark some sort of radio button selection, but in order to do that we have the following booleans:
Then when the user presses, for example, the purple button:
And so on and so forth for every color, then when displaying the color:
You get the idea.
Now the key to this is that each of these booleans are blocking each other, if
isRed
is true, the other booleans should always, always, be false.That’s the most important part. we could have coded this in such a way that you could select both red and blue, and then purple would be displayed, but for this solution to work, each of the booleans must be only true when the rest are false.
If you want to make the other interpretation, you should actually make enough booleans for each combination, like this:
Anyway, just remember you can do this any time your booleans are dependent from each other.
The solution
To solve the above problem, we can use an enumerator.
An enumerator is basically a type that can have one of a number of values, they look like this:
With the above, we can now make a
PossibleColors
variable and store the current selected color there:OLD:
NEW:
Then when setting the color:
OLD:
NEW:
finally, to display:
OLD:
NEW: