Flutter 3 is out and I've been experimenting a little.
I have used the ThemeExtension
yt ref: https://www.youtube.com/watch?v=8-szcYzFVao
api ref: https://api.flutter.dev/flutter/material/ThemeData/extensions.html
and its great. However I'm starting to realize and ask myself I could have done the same result if I created a class with static const as properties such as colors, textstyles or any related theming.
Can someone enlighten me if why I should use ThemeExtensions instead?
Static class way:
// Setup
class AppColors {
static const primaryColor = Color(0xFFFFFF);
static const secondaryColor = Color(0xFFFFFF);
}
// Use case inside build
return Container(
child: Text('Hello world'),
color: AppColors.primaryColor,
)
ThemeExtension Way
// Setup
class AppColors extends ThemeExtension<AppColors>{
final Color primaryColor;
final Color secondaryColor;
AppColors(this.primaryColor, this.secondaryColor);
// .
// ..
// ... some #overrides such as copyWith() and lerp()
}
// Use case inside build
final colors = Theme.of(context).extensions<AppColors>()!;
return Container(
child: Text('Hello world'),
color: colors.primaryColor,
)
As you can see here setting up for ThemeExtension is quite huge compared on using just the static classes for theme to achieve the same result.
I think you should use Theme and/or ThemeExtension because:
You can use Theme(and ThemeExtension) in order to get widgets reactive. If some value is changed(color, text style, etc...), it will notify all listeners and the build method of dependent widgets will be dispatched.
If you change the themeMode to dark/light, all application will react to update widgets that are using the theme.
You can get scoped theme, so if you need some widgets or screen to use a different theme from the entire application, it will be possible too.
You can share you ThemeData between multiple apps and his use will looks like very similar in all apps
Theme is a good choice to keep a consistent visual pattern
The child/children widgets just need to know the Theme, as a Single source of truth. If you need to change all screens button's color of the entire app, you will be safe and happy to do that in a single place :)
You don't need to use ThemeExtension to get all these mentioned benefits, but if you do, it will make simpler to keep a very good documented Design System as code.
Related
TL;DR
I need to render LaTeX equations in a Flutter text input field while the user is typing
The Goal
I am currently working on a Flutter project for both web/desktop where I need to be able to render LaTeX/TeX equations in an input field while the user is typing. For example, if the user types “sqrt”, a square root symbol appears where the sqrt was. This will allow for easy math input of complex functions, where the user can type something like:
“int 1 2 sqrt(sin(x/2)^2)”
While typing, this equation would continuously render in the same input box and end up looking like:
Example Equation Render
I plan to allow the user to use a lot of different mathematical symbols/functions.
An extremely similar implementation of what I am trying to achieve is the MathQuill project. However while MathQuill works well with JavaScript and React, I have not found a way to use it in Flutter. If there is a way to port it into Flutter that would work perfectly for my needs.
Requirements and Preferences
Requirements
Render the TeX in the same input box that they are typing in.
Render TeX in real time. The user should be able to see changes in their current text box while typing. A good (but very simple) demo of what I would like can be seen in the math entry boxes on Desmos. I do not want the user to have to press Enter to see their changes, it should happen while typing.
Cannot be click-based equation entry. I want the user to be able to type “sqrt”, “int”, “prod”, “sum”, etc. ... not click on a square root or integral button with some sort of on-screen keyboard. (For example, Flutter’s math_keyboard would not work for my project)
Flutter-based solution.
Preferences
I would prefer to use the KaTeX rendering engine due to it’s substantially increased speed over MathJax. I am still flexible here.
Current Attempts
I have looked through many packages on pub.dev, but have not been able to find a suitable package that suits my use case. I have managed to display TeX on the webpage using the flutter_tex package. For example:
Code:
import 'package:flutter/material.dart';
import 'package:flutter_tex/flutter_tex.dart';
void main() => runApp(TexApp());
class TexApp extends StatelessWidget {
const TexApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Testing flutter_tex")),
body: TeXView(
child: TeXViewColumn(children: [
// This widget displays data perfectly as it should, but it is static and
// so the user cannot dynamically edit the cell.
TeXViewDocument(r"""<p>
$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}$$</p>""",
style: TeXViewStyle.fromCSS(
'padding: 15px; color: black; background: white')),
]))),
);
}
}
Resulting webpage:
Resulting TeX equations in Flutter web application
However, the above implementation does not allow continuous input/dynamic rendering as the user types their equations which I need for my project.
I am new to Flutter and web development as a whole. However, I spent a lot of time learning React and Flutter and I am certain that I want to use Flutter moving forward for a variety of reasons.
Any help here would be extremely appreciated!
UPDATE: As of August 2022 I still have not found a solution to this issue!
I am currently trying to create a behavior for my drawer. I have a ListView with ExpansionTiles and normal ListTiles in-between the ExpansionTiles. If the user clicks an ExpansionTile it will open but when they select another ExpansionTile from the list, I would like it to collapse the previously open ExpansionTile. I know that ExpansionPanel.radio can be used to create this effect but it doesn't allow for regular ListTiles to be in the list as well. Anybody ever achieved this and can provide tips?
Simplified code:
bool newsExpanded = false;
bool weatherExpanded = false;
ExpansionTile(
initiallyExpanded: weatherExpanded,
textColor: Colors.black,
iconColor: Colors.grey,
backgroundColor: Colors.white38,
title: Text(
'Weather',
style: TextStyle(
fontSize: 16.0, fontWeight: FontWeight.bold),
),
onExpansionChanged: (onChanged) {
if (onChanged) {
setState(() {
newsExpanded = false;
weatherExpanded = true;
});
}
},
Both ExpansionTiles are the same code but with different bools.
Create a list of booleans for each one of your ExpansionTiles that holds whether the tile is open or not, start with all set to false and then whenever one of the tiles are open set the associated index in the list to true and all other to false.
After reading many SO posts and answers on this particular topic after facing this problem myself today it seems like there currently is no direct way to achieve this.
There are two workarounds that I have seen in the different answers:
The approach you also wanted to use. However, the problem is that initiallyExpanded is actually only called once when the ExpansionTile is built the very first time in the build method. So even if you change the bools for newsExpanded and weatherExpanded using setState(), initiallyExpanded is not called a second time. You can however cause initiallyExpanded to be triggered again by building a new ExpansionTile as replacement for the original one. This can be achieved by adding key: GlobalKey() to the ExpansionTile. The downside however, is that you lose the animation for the Widget because instead of changing its state you replace it with a new Widget that already renders as fully expanded.
You can also build your own ExpansionTile as a StatefulWidget. Thus you can assign a state to it and trigger expansion updates. The code behind this concept is quite extensive for what you want to achieve. If you're interested in using this approach you can find more informations and the neccessary code in this SO post.
I hope a functionality for this might be added in the future. Until then I'd consider thouroughly whether the functionality is really integral to your application.
I'm expecting that widgets in the display tree would be effected by a dividerColor having been set on my Theme in Flutter.
High up in my widget tree is:
MaterialApp(
/* ... */
theme: ThemeData(dividerColor: Colors.green)
/* ... */
);
And yet when using a Divider() inside my child widgets, the colour is not present. Only when I set the colour explicitly, does it appear, like so:
Divider(color: Colors.green)
My understanding was that the Theme's dividerColor would be used when no colour is specified?
Apologies; I had not restarted the application... 🤦♂️
Posting answer here in case it helps anyone else; Theme changes need a full restart of the application as hot-reloading does not seem to have an affect when changing theme properties.
Flutter has a class named TextTheme which can be used to set the styling of text across your entire app. It has several different fields which decide which style will be used on which Text widgets. The names hint at what they are for but some are also quite vague. I have not been able to find any documentation that explains exactly which widgets these styles will end up automatically getting applied to.
For example title will change the title text in an AppBar, which seems obvious enough... but how about body2 or display3?
Are there any resources that explain each of these and which widgets they will apply to (ideally visually)?
class TextTheme extends Diagnosticable {
const TextTheme({
this.display4,
this.display3,
this.display2,
this.display1,
this.headline,
this.title,
this.subhead,
this.body2,
this.body1,
this.caption,
this.button,
this.subtitle,
this.overline,
});
I think the document about TextTheme is here.
I don't think these styles will automatically apply to, it's just an option for you apply to text.
I need to know whether Flutter will render the iOS Cupertino style on iOS and Material on Android with a single codebase. I want to know this before starting to develop my app with Flutter.
If it's not, how would I manage the two different UI in a single code? I can't possibly use if/else everywhere over the code.
You should try the gallery app: https://play.google.com/store/apps/details?id=io.flutter.demo.gallery&hl=en In there, when you press the menu button you can set platform mechanics to be Mountain View (Android) or Cupertino (iOS). That way you can see how it's going to look in either platform.
Here is an example of selecting Cupertino or Material with code depending on the platform: https://medium.com/flutter-io/do-flutter-apps-dream-of-platform-aware-widgets-7d7ed7b4624d As far as I know you have to choose the widgets like this.
These capabilities are under development. I suggest to look into this library: https://pub.dartlang.org/packages/flutter_platform_widgets
Other than that, there is a lot you can do already, even without this library. Everything depends on your resources, requirements and complexity of your project.
For example, if you are working alone or in a small team, your project's complexity is probably not overwhelming, and your entire application will consist only of a couple of dozens of Widget derived classes at max. In this case, maybe you can deliberately program the platform-adaptive behavior in your custom Widget classes, so that you'll be happy working with them long-term.
This workflow is not always applicable, and may seem complicated at the beginning, but it will become more natural later.
As of recently, Flutter has added adaptive constructors for Switch, SwitchListTile, and CircularProgressIndicator which will help further progress platform-aware widgets within a single codebase. While these are the only three as of now, expect more in the future!
Below simple implementation:
class AdaptiveSwitch extends StatelessWidget {
const AdaptiveSwitch({Key key,
#required this.value,
#required this.onChanged,
this.activeColor,
this.dragStartBehavior = DragStartBehavior.start})
: assert(dragStartBehavior != null),
super(key: key);
final bool value;
final ValueChanged<bool> onChanged;
final Color activeColor;
final DragStartBehavior dragStartBehavior;
#override
Widget build(BuildContext context) {
return Theme.of(context).targetPlatform = TargetPlatform.iOS
? CupertinoSwitch(
value: value,
activeColor: activeColor,
onChanged: onChanged,
dragStartBehavior: dragStartBehavior,
)
: Switch(
value: value,
activeColor: activeColor,
onChanged: onChanged,
dragStartBehavior: dragStartBehavior,
);
}
}