Skip to content

Instantly share code, notes, and snippets.

@KDCinfo
Last active March 21, 2024 10:55
Show Gist options
  • Save KDCinfo/ba5d7209197f6c94231753e42baf36c6 to your computer and use it in GitHub Desktop.
Save KDCinfo/ba5d7209197f6c94231753e42baf36c6 to your computer and use it in GitHub Desktop.
iOS Unusual App Crashing - Flutter 3.21
import 'dart:developer';
import 'package:flutter/material.dart';
/// To recreate:
///
/// 1. Using any Flutter version merged after Mar 7, 2024, (e.g. v3.21),
/// build and run the provided code example on an iOS device or simulator.
/// 2. Tap the title in the AppBar.
///
/// Note: If code was changed and hot-refreshed, the first tap could be
/// buffered, and the 2nd tap may cause the crash (if expected).
/// Tapping two or more times for testing is recommended
/// (unless it crashes on the first tap, obv.)
///
/// 3. Observe the app:
/// 3a. Crashes (iPad simulator) with the console message,
/// "Lost connection to device.", or,
/// 3b. Hits a raster-level breakpoint in Xcode (physical iPad).
///
/// - Anecdotally, an iPad simulator crashed, while a physical iPad
/// popped up a breakpoint in Xcode (despite running in VS Code). YMMV
///
/// - When the physical device hit the breakpoint in Xcode, I 'continued'
/// the step-through over 60 times, and eventually just stopped the runner.
///
/// - Other than the one-line console output after the
/// crash on the simulator, no logs were dumped in either case.
///
/// Expected result: Tapping the title in the AppBar one or more times,
/// the console log should output one line per tap,
/// and the app should not crash.
///
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: AppConfig.appTitle,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.amberAccent,
title: Center(
child: ColoredBox(
color: Colors.lightBlueAccent,
child: InkWell(
onTap: () {
log('[main.dart] InkWell onTap');
},
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
child: Text(
AppConfig.appBarTitleTap,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
),
body: Align(
alignment: Alignment.topCenter,
child: SizedBox(
width: 400,
height: 400,
/// In the comments below, "it" means tapping the AppBar title.
///
/// The code as-is will crash the app on iOS when the AppBar
/// title is tapped, until one of the options below are changed.
///
/// Removing this `SingleChildScrollView` will make it work. [#1]
/// (Not the column.)
child: SingleChildScrollView(
child: Column(
children: [
///
/// After removing the `SingleChildScrollView` above,
/// which will make it work, removing this `Padding`
/// will then make it crash. [#1b]
const Padding(
padding: EdgeInsets.only(top: 20),
child: Text(
AppConfig.appDesc,
style: TextStyle(
fontSize: 22,
),
),
),
/// This is the outside wrapper `Card`.
Card(
/// Reverting the changes above (resetting it to crash),
/// commenting out this property will work. [#2]
/// For [#2] and [#3] it would seem you can't have
/// a nested `Card`, each with its own clipBehavior.
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
color: Colors.orangeAccent,
elevation: AppConfig.cardElevationWrapper,
margin: const EdgeInsets.symmetric(vertical: 30),
/// A wrapper Form for the TextFormField.
child: Form(
key: AppConfig.formKey,
///
/// This is the inside `Card`.
child: Card(
///
/// Commenting out this property will work. [#3]
clipBehavior: Clip.antiAlias,
/// Commenting out this property will work. [#4]
/// Also, copying the `shape` above will work [#4b].
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
color: Colors.lightGreenAccent,
elevation: AppConfig.cardElevationInside,
margin: const EdgeInsets.symmetric(
horizontal: AppConfig.spacingHorizontal,
vertical: AppConfig.spacingVertical,
),
/// Changing this to a Text widget will work. [#5]
/// A FormField may also crash the app (tried once).
child: TextFormField(
decoration: const InputDecoration(
hintText: AppConfig.textFormFieldInitialValue,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: AppConfig.spacingHorizontal,
vertical: AppConfig.spacingVertical,
),
),
),
// child: Text(
// AppConfig.textFormFieldInitialValue,
// style: TextStyle(
// fontSize: 22,
// ),
// ),
),
),
),
],
),
),
),
),
),
);
}
}
abstract class AppConfig {
static final GlobalKey<FormState> formKey = GlobalKey<FormState>();
static const String appTitle = 'Debugging an iOS Crash';
static const String appDesc = 'Debugging an unusual iOS Crash with various widgets';
static const String appBarTitleTap = '[ Tap to Crash (or log) ]';
static const String textFormFieldInitialValue = 'TextFormField or FormField';
static const double cardElevationWrapper = 5;
static const double cardElevationInside = 10;
static const double spacingHorizontal = 20;
static const double spacingVertical = 10;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment