blob: 88d48b41dbdf7f50d0480757f50d8131ab1b062b [file] [log] [blame]
// ignore_for_file:implementation_imports
import 'dart:convert';
import 'package:flutter/widgets.dart';
import 'package:flutter_mobx/src/stateless_observer_widget.dart';
/// `true` if a stack frame indicating where an [Observer] was created should be
/// included in its name. This is useful during debugging to identify the source
/// of warnings or errors.
///
/// Note that stack frames are only included in debug builds.
bool debugAddStackTraceInObserverName = true;
/// A [StatelessObserverWidget] that delegate its [build] method to [builder].
///
/// See also:
///
/// - [Builder], which is the same thing but for [StatelessWidget] instead.
class Observer extends StatelessObserverWidget
// Implements Builder to import the documentation of `builder`
implements
// ignore: avoid_implementing_value_types
Builder {
// ignore: prefer_const_constructors_in_immutables
Observer({
Key? key,
required this.builder,
String? name,
bool? warnWhenNoObservables,
}) : debugConstructingStackFrame = debugFindConstructingStackFrame(),
super(
key: key,
name: name,
warnWhenNoObservables: warnWhenNoObservables,
);
@override
final WidgetBuilder builder;
/// The stack frame pointing to the source that constructed this instance.
final String? debugConstructingStackFrame;
@override
String getName() =>
super.getName() +
(debugConstructingStackFrame != null
? '\n$debugConstructingStackFrame'
: '');
@override
Widget build(BuildContext context) => builder(context);
/// Matches constructor stack frames, in both VM and web environments.
static final _constructorStackFramePattern = RegExp(r'\bnew\b');
static final _stackFrameCleanUpPattern = RegExp(r'^#\d+\s+(.*)$');
/// Finds the first non-constructor frame in the stack trace.
///
/// [stackTrace] defaults to [StackTrace.current].
@visibleForTesting
static String? debugFindConstructingStackFrame([StackTrace? stackTrace]) {
String? stackFrame;
assert(() {
if (debugAddStackTraceInObserverName) {
final stackTraceString = (stackTrace ?? StackTrace.current).toString();
final rawStackFrame = LineSplitter.split(stackTraceString)
// We are skipping frames representing:
// 1. The anonymous function in the assert
// 2. The debugFindConstructingStackFrame method
// 3. The constructor invoking debugFindConstructingStackFrame
//
// The 4th frame is either user source (which is what we want), or
// an Observer subclass' constructor (which we skip past with the
// regex)
.skip(3)
// Search for the first non-constructor frame
.firstWhere(
(frame) => !_constructorStackFramePattern.hasMatch(frame),
orElse: () => '');
final stackFrameCore =
_stackFrameCleanUpPattern.firstMatch(rawStackFrame)?.group(1);
final cleanedStackFrame = stackFrameCore == null
? null
: 'Observer constructed from: $stackFrameCore';
stackFrame = cleanedStackFrame;
}
return true;
}());
return stackFrame;
}
}