Mounir Bouaiche

Clean codes & add features & fix bugs

Features/Bugs:
- Feature: You can select what classes needs to be rebuilt instead of rebuilding everything, if you have widget A, either addi SU mixin or add 'A' to list ScreenUtilInit.responsiveWidgets
- Feature: Using ScreenUtilInit.builder is optional (use it only when using library in theme)
- Bug: Second call to ScreenUtil.init ignores any existing values and uses the default values when not provided, use ScreenUtil.configure instead
- Bug: ScreenUtil.ensureScreenSize raises an overflow error
@@ -139,3 +139,6 @@ doc @@ -139,3 +139,6 @@ doc
139 .lock 139 .lock
140 coverage* 140 coverage*
141 *.lock 141 *.lock
  142 +
  143 +# Don't commit .fvm directory containing machine-specific symlink to sdk & flutter version
  144 +**/.fvm
  1 +/////////////////////////////////////////////////////////////////////////
  2 +/// Generated via plugin: flutter_screenutil_generator - Do Not Touch ///
  3 +/////////////////////////////////////////////////////////////////////////
  4 +
  5 +part of 'responsive_widgets.su.dart';
  6 +
  7 +const _responsiveWidgets = {
  8 + 'MyThemedApp',
  9 + 'MyApp',
  10 + 'HomePageScaffold',
  11 +};
  1 +part 'responsive_widgets.g.dart';
  2 +
  3 +get responsiveWidgets => _responsiveWidgets;
  1 +import 'package:example/responsive_widgets.su.dart';
1 import 'package:example/src/home.dart'; 2 import 'package:example/src/home.dart';
2 import 'package:flutter/material.dart'; 3 import 'package:flutter/material.dart';
  4 +// import 'package:example/allowed_classes.su.dart';
3 import 'package:flutter_screenutil/flutter_screenutil.dart'; 5 import 'package:flutter_screenutil/flutter_screenutil.dart';
4 6
5 class MyApp extends StatelessWidget { 7 class MyApp extends StatelessWidget {
@@ -9,9 +11,8 @@ class MyApp extends StatelessWidget { @@ -9,9 +11,8 @@ class MyApp extends StatelessWidget {
9 Widget build(BuildContext context) { 11 Widget build(BuildContext context) {
10 // In first method you only need to wrap [MaterialApp] with [ScreenUtilInit] and that's it 12 // In first method you only need to wrap [MaterialApp] with [ScreenUtilInit] and that's it
11 return ScreenUtilInit( 13 return ScreenUtilInit(
12 - useInheritedMediaQuery: false,  
13 - builder: (_, child) {  
14 - return MaterialApp( 14 + responsiveWidgets: responsiveWidgets,
  15 + child: MaterialApp(
15 debugShowCheckedModeBanner: false, 16 debugShowCheckedModeBanner: false,
16 title: 'First Method', 17 title: 'First Method',
17 // You can use the library anywhere in the app even in theme 18 // You can use the library anywhere in the app even in theme
@@ -21,10 +22,8 @@ class MyApp extends StatelessWidget { @@ -21,10 +22,8 @@ class MyApp extends StatelessWidget {
21 .black 22 .black
22 .apply(fontSizeFactor: 1), 23 .apply(fontSizeFactor: 1),
23 ), 24 ),
24 - home: child,  
25 - );  
26 - },  
27 - child: const HomePage(title: 'First Method'), 25 + home: const HomePage(title: 'First Method'),
  26 + ),
28 ); 27 );
29 } 28 }
30 } 29 }
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; @@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
2 import 'package:flutter/services.dart'; 2 import 'package:flutter/services.dart';
3 import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 import 'package:flutter_screenutil/flutter_screenutil.dart';
4 4
5 -class HomePageScaffold extends StatelessWidget { 5 +class HomePageScaffold extends StatelessWidget with SU {
6 const HomePageScaffold({Key? key, this.title = ''}) : super(key: key); 6 const HomePageScaffold({Key? key, this.title = ''}) : super(key: key);
7 7
8 void printScreenInformation(BuildContext context) { 8 void printScreenInformation(BuildContext context) {
@@ -25,6 +25,9 @@ dev_dependencies: @@ -25,6 +25,9 @@ dev_dependencies:
25 flutter_test: 25 flutter_test:
26 sdk: flutter 26 sdk: flutter
27 test: ^1.15.7 27 test: ^1.15.7
  28 + build_runner:
  29 + flutter_screenutil_generator:
  30 + path: ../../flutter_screenutil_generator
28 31
29 flutter: 32 flutter:
30 uses-material-design: true 33 uses-material-design: true
@@ -10,3 +10,4 @@ export 'src/r_sizedbox.dart'; @@ -10,3 +10,4 @@ export 'src/r_sizedbox.dart';
10 export 'src/screen_util.dart'; 10 export 'src/screen_util.dart';
11 export 'src/screenutil_init.dart'; 11 export 'src/screenutil_init.dart';
12 export 'src/size_extension.dart'; 12 export 'src/size_extension.dart';
  13 +export 'src/screenutil_mixin.dart';
@@ -4,8 +4,7 @@ @@ -4,8 +4,7 @@
4 */ 4 */
5 5
6 import 'dart:math' show min, max; 6 import 'dart:math' show min, max;
7 -import 'dart:ui' show FlutterView;  
8 -import 'dart:async' show Completer; 7 +import 'dart:ui' as ui show FlutterView;
9 8
10 import 'package:flutter/widgets.dart'; 9 import 'package:flutter/widgets.dart';
11 10
@@ -20,10 +19,8 @@ class ScreenUtil { @@ -20,10 +19,8 @@ class ScreenUtil {
20 ///屏幕方向 19 ///屏幕方向
21 late Orientation _orientation; 20 late Orientation _orientation;
22 21
23 - late double _screenWidth;  
24 - late double _screenHeight;  
25 late bool _minTextAdapt; 22 late bool _minTextAdapt;
26 - BuildContext? _context; 23 + late MediaQueryData _data;
27 late bool _splitScreenMode; 24 late bool _splitScreenMode;
28 25
29 ScreenUtil._(); 26 ScreenUtil._();
@@ -32,6 +29,22 @@ class ScreenUtil { @@ -32,6 +29,22 @@ class ScreenUtil {
32 return _instance; 29 return _instance;
33 } 30 }
34 31
  32 + factory ScreenUtil.init(
  33 + BuildContext context, {
  34 + Size designSize = defaultSize,
  35 + bool splitScreenMode = false,
  36 + bool minTextAdapt = false,
  37 + }) {
  38 + configure(
  39 + data: MediaQuery.maybeOf(context),
  40 + designSize: designSize,
  41 + minTextAdapt: minTextAdapt,
  42 + splitScreenMode: splitScreenMode,
  43 + );
  44 +
  45 + return _instance;
  46 + }
  47 +
35 /// Manually wait for window size to be initialized 48 /// Manually wait for window size to be initialized
36 /// 49 ///
37 /// `Recommended` to use before you need access window size 50 /// `Recommended` to use before you need access window size
@@ -55,19 +68,25 @@ class ScreenUtil { @@ -55,19 +68,25 @@ class ScreenUtil {
55 /// ) 68 /// )
56 /// ``` 69 /// ```
57 static Future<void> ensureScreenSize([ 70 static Future<void> ensureScreenSize([
58 - FlutterView? window, 71 + ui.FlutterView? window,
59 Duration duration = const Duration(milliseconds: 10), 72 Duration duration = const Duration(milliseconds: 10),
60 ]) async { 73 ]) async {
61 final binding = WidgetsFlutterBinding.ensureInitialized(); 74 final binding = WidgetsFlutterBinding.ensureInitialized();
62 - window ??= WidgetsBinding.instance.platformDispatcher.implicitView;  
63 -  
64 - if (window?.physicalGeometry.isEmpty == true) {  
65 - return Future.delayed(duration, () async {  
66 binding.deferFirstFrame(); 75 binding.deferFirstFrame();
67 - await ensureScreenSize(window, duration);  
68 - return binding.allowFirstFrame();  
69 - }); 76 +
  77 + await Future.doWhile(() {
  78 + if (window == null) {
  79 + window = binding.platformDispatcher.implicitView;
70 } 80 }
  81 +
  82 + if (window == null || window!.physicalGeometry.isEmpty == true) {
  83 + return Future.delayed(duration, () => true);
  84 + }
  85 +
  86 + return false;
  87 + });
  88 +
  89 + binding.allowFirstFrame();
71 } 90 }
72 91
73 Set<Element>? _elementsToRebuild; 92 Set<Element>? _elementsToRebuild;
@@ -89,43 +108,30 @@ class ScreenUtil { @@ -89,43 +108,30 @@ class ScreenUtil {
89 } 108 }
90 109
91 /// Initializing the library. 110 /// Initializing the library.
92 - static Future<void> init(BuildContext context,  
93 - {Size designSize = defaultSize,  
94 - bool splitScreenMode = false,  
95 - bool minTextAdapt = false,  
96 - bool scaleByHeight = false}) async {  
97 - final mediaQueryContext =  
98 - context.getElementForInheritedWidgetOfExactType<MediaQuery>();  
99 -  
100 - final initCompleter = Completer<void>(); 111 + static void configure({
  112 + MediaQueryData? data,
  113 + Size? designSize,
  114 + bool? splitScreenMode,
  115 + bool? minTextAdapt,
  116 + }) {
  117 + if (data != null) _instance._data = data;
101 118
102 - WidgetsFlutterBinding.ensureInitialized().addPostFrameCallback((_) {  
103 - mediaQueryContext?.visitChildElements((el) => _instance._context = el);  
104 - if (_instance._context != null) initCompleter.complete();  
105 - }); 119 + final deviceData = _instance._data.nonEmptySizeOrNull();
  120 + final deviceSize = deviceData?.size ?? designSize ?? _instance._uiSize;
106 121
107 - final deviceData = MediaQuery.maybeOf(context).nonEmptySizeOrNull(); 122 + if (designSize != null) _instance._uiSize = designSize;
108 123
109 - final deviceSize = deviceData?.size ?? designSize;  
110 final orientation = deviceData?.orientation ?? 124 final orientation = deviceData?.orientation ??
111 (deviceSize.width > deviceSize.height 125 (deviceSize.width > deviceSize.height
112 ? Orientation.landscape 126 ? Orientation.landscape
113 : Orientation.portrait); 127 : Orientation.portrait);
114 128
115 _instance 129 _instance
116 - .._context = scaleByHeight ? null : context  
117 - .._uiSize = designSize  
118 - .._splitScreenMode = splitScreenMode  
119 - .._minTextAdapt = minTextAdapt  
120 - .._orientation = orientation  
121 - .._screenWidth = scaleByHeight  
122 - ? (deviceSize.height * designSize.width) / designSize.height  
123 - : deviceSize.width  
124 - .._screenHeight = deviceSize.height; 130 + .._minTextAdapt = minTextAdapt ?? _instance._minTextAdapt
  131 + .._splitScreenMode = splitScreenMode ?? _instance._splitScreenMode
  132 + .._orientation = orientation;
125 133
126 _instance._elementsToRebuild?.forEach((el) => el.markNeedsBuild()); 134 _instance._elementsToRebuild?.forEach((el) => el.markNeedsBuild());
127 -  
128 - return initCompleter.future;  
129 } 135 }
130 136
131 ///获取屏幕方向 137 ///获取屏幕方向
@@ -134,33 +140,27 @@ class ScreenUtil { @@ -134,33 +140,27 @@ class ScreenUtil {
134 140
135 /// 每个逻辑像素的字体像素数,字体的缩放比例 141 /// 每个逻辑像素的字体像素数,字体的缩放比例
136 /// The number of font pixels for each logical pixel. 142 /// The number of font pixels for each logical pixel.
137 - double get textScaleFactor =>  
138 - _context != null ? MediaQuery.of(_context!).textScaleFactor : 1; 143 + double get textScaleFactor => _data.textScaleFactor;
139 144
140 /// 设备的像素密度 145 /// 设备的像素密度
141 /// The size of the media in logical pixels (e.g, the size of the screen). 146 /// The size of the media in logical pixels (e.g, the size of the screen).
142 - double? get pixelRatio =>  
143 - _context != null ? MediaQuery.of(_context!).devicePixelRatio : 1; 147 + double? get pixelRatio => _data.devicePixelRatio;
144 148
145 /// 当前设备宽度 dp 149 /// 当前设备宽度 dp
146 /// The horizontal extent of this size. 150 /// The horizontal extent of this size.
147 - double get screenWidth =>  
148 - _context != null ? MediaQuery.of(_context!).size.width : _screenWidth; 151 + double get screenWidth => _data.size.width;
149 152
150 ///当前设备高度 dp 153 ///当前设备高度 dp
151 ///The vertical extent of this size. dp 154 ///The vertical extent of this size. dp
152 - double get screenHeight =>  
153 - _context != null ? MediaQuery.of(_context!).size.height : _screenHeight; 155 + double get screenHeight => _data.size.height;
154 156
155 /// 状态栏高度 dp 刘海屏会更高 157 /// 状态栏高度 dp 刘海屏会更高
156 /// The offset from the top, in dp 158 /// The offset from the top, in dp
157 - double get statusBarHeight =>  
158 - _context == null ? 0 : MediaQuery.of(_context!).padding.top; 159 + double get statusBarHeight => _data.padding.top;
159 160
160 /// 底部安全区距离 dp 161 /// 底部安全区距离 dp
161 /// The offset from the bottom, in dp 162 /// The offset from the bottom, in dp
162 - double get bottomBarHeight =>  
163 - _context == null ? 0 : MediaQuery.of(_context!).padding.bottom; 163 + double get bottomBarHeight => _data.padding.bottom;
164 164
165 /// 实际尺寸与UI设计的比例 165 /// 实际尺寸与UI设计的比例
166 /// The ratio of actual width to UI design 166 /// The ratio of actual width to UI design
1 import 'package:flutter/widgets.dart'; 1 import 'package:flutter/widgets.dart';
2 2
  3 +import 'screenutil_mixin.dart';
3 import 'screen_util.dart'; 4 import 'screen_util.dart';
4 5
5 typedef RebuildFactor = bool Function(MediaQueryData old, MediaQueryData data); 6 typedef RebuildFactor = bool Function(MediaQueryData old, MediaQueryData data);
@@ -9,9 +10,7 @@ typedef ScreenUtilInitBuilder = Widget Function( @@ -9,9 +10,7 @@ typedef ScreenUtilInitBuilder = Widget Function(
9 Widget? child, 10 Widget? child,
10 ); 11 );
11 12
12 -class RebuildFactors {  
13 - const RebuildFactors._();  
14 - 13 +abstract class RebuildFactors {
15 static bool size(MediaQueryData old, MediaQueryData data) { 14 static bool size(MediaQueryData old, MediaQueryData data) {
16 return old.size != data.size; 15 return old.size != data.size;
17 } 16 }
@@ -24,35 +23,43 @@ class RebuildFactors { @@ -24,35 +23,43 @@ class RebuildFactors {
24 return old.viewInsets != data.viewInsets; 23 return old.viewInsets != data.viewInsets;
25 } 24 }
26 25
27 - static bool all(MediaQueryData old, MediaQueryData data) { 26 + static bool change(MediaQueryData old, MediaQueryData data) {
28 return old != data; 27 return old != data;
29 } 28 }
  29 +
  30 + static bool always(MediaQueryData _, MediaQueryData __) {
  31 + return true;
  32 + }
  33 +
  34 + static bool none(MediaQueryData _, MediaQueryData __) {
  35 + return false;
  36 + }
30 } 37 }
31 38
32 class ScreenUtilInit extends StatefulWidget { 39 class ScreenUtilInit extends StatefulWidget {
33 /// A helper widget that initializes [ScreenUtil] 40 /// A helper widget that initializes [ScreenUtil]
34 - const ScreenUtilInit(  
35 - {Key? key,  
36 - required this.builder, 41 + const ScreenUtilInit({
  42 + Key? key,
  43 + this.builder,
37 this.child, 44 this.child,
38 this.rebuildFactor = RebuildFactors.size, 45 this.rebuildFactor = RebuildFactors.size,
39 this.designSize = ScreenUtil.defaultSize, 46 this.designSize = ScreenUtil.defaultSize,
40 this.splitScreenMode = false, 47 this.splitScreenMode = false,
41 this.minTextAdapt = false, 48 this.minTextAdapt = false,
42 this.useInheritedMediaQuery = false, 49 this.useInheritedMediaQuery = false,
43 - this.scaleByHeight = false})  
44 - : super(key: key); 50 + this.responsiveWidgets,
  51 + }) : super(key: key);
45 52
46 - final ScreenUtilInitBuilder builder; 53 + final ScreenUtilInitBuilder? builder;
47 final Widget? child; 54 final Widget? child;
48 final bool splitScreenMode; 55 final bool splitScreenMode;
49 final bool minTextAdapt; 56 final bool minTextAdapt;
50 final bool useInheritedMediaQuery; 57 final bool useInheritedMediaQuery;
51 - final bool scaleByHeight;  
52 final RebuildFactor rebuildFactor; 58 final RebuildFactor rebuildFactor;
53 59
54 /// The [Size] of the device in the design draft, in dp 60 /// The [Size] of the device in the design draft, in dp
55 final Size designSize; 61 final Size designSize;
  62 + final Iterable<String>? responsiveWidgets;
56 63
57 @override 64 @override
58 State<ScreenUtilInit> createState() => _ScreenUtilInitState(); 65 State<ScreenUtilInit> createState() => _ScreenUtilInitState();
@@ -60,127 +67,85 @@ class ScreenUtilInit extends StatefulWidget { @@ -60,127 +67,85 @@ class ScreenUtilInit extends StatefulWidget {
60 67
61 class _ScreenUtilInitState extends State<ScreenUtilInit> 68 class _ScreenUtilInitState extends State<ScreenUtilInit>
62 with WidgetsBindingObserver { 69 with WidgetsBindingObserver {
  70 + late final Iterable<String>? _canMarkedToBuild;
63 MediaQueryData? _mediaQueryData; 71 MediaQueryData? _mediaQueryData;
  72 + final WidgetsBinding _binding = WidgetsBinding.instance;
64 73
65 - bool wrappedInMediaQuery = false; 74 + @override
  75 + void initState() {
  76 + _canMarkedToBuild = widget.responsiveWidgets;
  77 + super.initState();
  78 + _binding.addObserver(this);
  79 + }
66 80
67 - WidgetsBinding get binding => WidgetsFlutterBinding.ensureInitialized(); 81 + @override
  82 + void didChangeMetrics() {
  83 + super.didChangeMetrics();
  84 + _revalidate();
  85 + }
68 86
69 - MediaQueryData get mediaQueryData => _mediaQueryData!; 87 + @override
  88 + void didChangeDependencies() {
  89 + super.didChangeDependencies();
  90 + _revalidate();
  91 + }
70 92
71 - MediaQueryData get newData {  
72 - final data = MediaQuery.maybeOf(context); 93 + MediaQueryData? _newData() {
  94 + MediaQueryData? mq = MediaQuery.maybeOf(context);
  95 + if (mq == null) mq = MediaQueryData.fromView(View.of(context));
73 96
74 - if (data != null) {  
75 - if (widget.useInheritedMediaQuery) {  
76 - wrappedInMediaQuery = true;  
77 - }  
78 - return data; 97 + return mq;
79 } 98 }
80 99
81 - return MediaQueryData.fromView(View.of(context)); 100 + void _markNeedsBuildIfAllowed(Element el) {
  101 + if (_canMarkedToBuild != null) {
  102 + final widget = el.widget, widgetName = el.widget.runtimeType.toString();
  103 +
  104 + if (widget is! SU && !_canMarkedToBuild!.contains(widgetName)) return;
82 } 105 }
83 106
84 - _updateTree(Element el) {  
85 el.markNeedsBuild(); 107 el.markNeedsBuild();
86 - el.visitChildren(_updateTree);  
87 } 108 }
88 109
89 - @override  
90 - void initState() {  
91 - super.initState();  
92 - binding.addObserver(this); 110 + void _updateTree(Element el) {
  111 + _markNeedsBuildIfAllowed(el);
  112 + el.visitChildren(_updateTree);
93 } 113 }
94 114
95 - @override  
96 - void didChangeMetrics() {  
97 - final old = _mediaQueryData!;  
98 - final data = newData; 115 + void _revalidate([void Function()? callback]) {
  116 + final oldData = _mediaQueryData;
  117 + final newData = _newData();
  118 +
  119 + if (newData == null) return;
99 120
100 - if (widget.scaleByHeight || widget.rebuildFactor(old, data)) {  
101 - _mediaQueryData = data; 121 + if (oldData == null || widget.rebuildFactor(oldData, newData)) {
  122 + setState(() {
  123 + _mediaQueryData = newData;
102 _updateTree(context as Element); 124 _updateTree(context as Element);
  125 + callback?.call();
  126 + });
103 } 127 }
104 } 128 }
105 129
106 @override 130 @override
107 - void didChangeDependencies() {  
108 - super.didChangeDependencies();  
109 - if (_mediaQueryData == null) _mediaQueryData = newData;  
110 - didChangeMetrics();  
111 - } 131 + Widget build(BuildContext context) {
  132 + final mq = _mediaQueryData;
112 133
113 - @override  
114 - void dispose() {  
115 - binding.removeObserver(this);  
116 - super.dispose();  
117 - } 134 + if (mq == null) return SizedBox.shrink();
118 135
119 - @override  
120 - Widget build(BuildContext _context) {  
121 - if (mediaQueryData.size == Size.zero) return const SizedBox.shrink();  
122 - if (!wrappedInMediaQuery) {  
123 - return MediaQuery(  
124 - data: mediaQueryData,  
125 - child: Builder(  
126 - builder: (__context) {  
127 - ScreenUtil.init(  
128 - __context, 136 + ScreenUtil.configure(
  137 + data: mq,
129 designSize: widget.designSize, 138 designSize: widget.designSize,
130 splitScreenMode: widget.splitScreenMode, 139 splitScreenMode: widget.splitScreenMode,
131 minTextAdapt: widget.minTextAdapt, 140 minTextAdapt: widget.minTextAdapt,
132 - scaleByHeight: widget.scaleByHeight,  
133 - );  
134 - final deviceData = MediaQuery.maybeOf(__context);  
135 - final deviceSize = deviceData?.size ?? widget.designSize;  
136 - return MediaQuery(  
137 - data: MediaQueryData.fromView(View.of(__context)),  
138 - child: Container(  
139 - width: deviceSize.width,  
140 - height: deviceSize.height,  
141 - child: FittedBox(  
142 - fit: BoxFit.none,  
143 - alignment: Alignment.center,  
144 - child: Container(  
145 - width: widget.scaleByHeight  
146 - ? (deviceSize.height * widget.designSize.width) /  
147 - widget.designSize.height  
148 - : deviceSize.width,  
149 - height: deviceSize.height,  
150 - child: widget.builder(__context, widget.child),  
151 - ),  
152 - ),  
153 - ),  
154 - );  
155 - },  
156 - ),  
157 ); 141 );
  142 +
  143 + return widget.builder?.call(context, widget.child) ?? widget.child!;
158 } 144 }
159 145
160 - ScreenUtil.init(  
161 - _context,  
162 - designSize: widget.designSize,  
163 - splitScreenMode: widget.splitScreenMode,  
164 - minTextAdapt: widget.minTextAdapt,  
165 - scaleByHeight: widget.scaleByHeight,  
166 - );  
167 - final deviceData = MediaQuery.maybeOf(_context);  
168 - final deviceSize = deviceData?.size ?? widget.designSize;  
169 - return Container(  
170 - width: deviceSize.width,  
171 - height: deviceSize.height,  
172 - child: FittedBox(  
173 - fit: BoxFit.none,  
174 - alignment: Alignment.center,  
175 - child: Container(  
176 - width: widget.scaleByHeight  
177 - ? (deviceSize.height * widget.designSize.width) /  
178 - widget.designSize.height  
179 - : deviceSize.width,  
180 - height: deviceSize.height,  
181 - child: widget.builder(_context, widget.child),  
182 - ),  
183 - ),  
184 - ); 146 + @override
  147 + void dispose() {
  148 + _binding.removeObserver(this);
  149 + super.dispose();
185 } 150 }
186 } 151 }
  1 +import 'package:flutter/widgets.dart';
  2 +
  3 +mixin SU on Widget {}
@@ -14,3 +14,4 @@ dependencies: @@ -14,3 +14,4 @@ dependencies:
14 dev_dependencies: 14 dev_dependencies:
15 flutter_test: 15 flutter_test:
16 sdk: flutter 16 sdk: flutter
  17 + build_runner: