[{"data":1,"prerenderedAt":3248},["ShallowReactive",2],{"page-\u002Fdart\u002F08-null-safety":3},{"id":4,"title":5,"body":6,"description":3241,"extension":3242,"meta":3243,"navigation":51,"path":3244,"seo":3245,"stem":3246,"__hash__":3247},"content\u002Fdart\u002F08-null-safety.md","Dart — Sound Null Safety, Promotion & Flow Analysis",{"type":7,"value":8,"toc":3227},"minimark",[9,13,18,415,420,991,997,1421,1425,1960,1964,2417,2427,2614,2618,2719,2723,2902,2906,2910,3031,3034,3223],[10,11,5],"h1",{"id":12},"dart-sound-null-safety-promotion-flow-analysis",[14,15,17],"h2",{"id":16},"type-promotion-the-complete-rule-set","Type Promotion — The Complete Rule Set",[19,20,22],"code-wrapper",{"language":21},"dart",[23,24,28],"pre",{"className":25,"code":26,"language":21,"meta":27,"style":27},"language-dart shiki shiki-themes github-light github-dark","\u002F\u002F Type promotion narrows a type within a scope based on control flow.\n\u002F\u002F It works ONLY on local variables (not fields, not across closures, not across await).\n\n\u002F\u002F ── 1. `if (x != null)` promotion ──\nString? name = getInput();\nif (name != null) {\n  \u002F\u002F name is promoted: String? → String. No `!` or `?.` needed.\n  print(name.length);\n}\n\u002F\u002F Outside the if: name is still String?.\n\n\u002F\u002F ── 2. `if (x is T)` promotion ──\nObject obj = 'hello';\nif (obj is String) {\n  print(obj.length);  \u002F\u002F obj promoted: Object → String\n}\n\u002F\u002F Outside: obj is still Object.\n\n\u002F\u002F ── 3. Early return promotion ──\nString process(String? input) {\n  if (input == null) return 'default';\n  \u002F\u002F After the null-return, input is promoted to String for the rest of the function.\n  return input.toUpperCase();  \u002F\u002F no `!` needed\n}\n\n\u002F\u002F ── 4. `??` default promotion ──\nString? maybeName = getInput();\nString name = maybeName ?? 'Anonymous';\n\u002F\u002F `name` is non-nullable String (if maybeName was null, it's 'Anonymous').\n\n\u002F\u002F ── 5. Definite assignment (late locals) ──\nlate String result;\nif (condition) {\n  result = 'yes';\n} else {\n  result = 'no';\n}\nprint(result);  \u002F\u002F ✓ assigned in all branches — definite assignment analysis\n","",[29,30,31,40,46,53,59,84,102,108,117,123,129,134,140,158,174,185,190,196,201,207,225,250,256,274,279,284,290,306,325,331,336,342,353,361,374,386,398,403],"code",{"__ignoreMap":27},[32,33,36],"span",{"class":34,"line":35},"line",1,[32,37,39],{"class":38},"sdCPZ","\u002F\u002F Type promotion narrows a type within a scope based on control flow.\n",[32,41,43],{"class":34,"line":42},2,[32,44,45],{"class":38},"\u002F\u002F It works ONLY on local variables (not fields, not across closures, not across await).\n",[32,47,49],{"class":34,"line":48},3,[32,50,52],{"emptyLinePlaceholder":51},true,"\n",[32,54,56],{"class":34,"line":55},4,[32,57,58],{"class":38},"\u002F\u002F ── 1. `if (x != null)` promotion ──\n",[32,60,62,66,70,74,77,81],{"class":34,"line":61},5,[32,63,65],{"class":64},"snvgF","String",[32,67,69],{"class":68},"svdQ7","?",[32,71,73],{"class":72},"ssxIu"," name ",[32,75,76],{"class":68},"=",[32,78,80],{"class":79},"sIsaT"," getInput",[32,82,83],{"class":72},"();\n",[32,85,87,90,93,96,99],{"class":34,"line":86},6,[32,88,89],{"class":68},"if",[32,91,92],{"class":72}," (name ",[32,94,95],{"class":68},"!=",[32,97,98],{"class":64}," null",[32,100,101],{"class":72},") {\n",[32,103,105],{"class":34,"line":104},7,[32,106,107],{"class":38},"  \u002F\u002F name is promoted: String? → String. No `!` or `?.` needed.\n",[32,109,111,114],{"class":34,"line":110},8,[32,112,113],{"class":79},"  print",[32,115,116],{"class":72},"(name.length);\n",[32,118,120],{"class":34,"line":119},9,[32,121,122],{"class":72},"}\n",[32,124,126],{"class":34,"line":125},10,[32,127,128],{"class":38},"\u002F\u002F Outside the if: name is still String?.\n",[32,130,132],{"class":34,"line":131},11,[32,133,52],{"emptyLinePlaceholder":51},[32,135,137],{"class":34,"line":136},12,[32,138,139],{"class":38},"\u002F\u002F ── 2. `if (x is T)` promotion ──\n",[32,141,143,146,149,151,155],{"class":34,"line":142},13,[32,144,145],{"class":64},"Object",[32,147,148],{"class":72}," obj ",[32,150,76],{"class":68},[32,152,154],{"class":153},"sJ6F3"," 'hello'",[32,156,157],{"class":72},";\n",[32,159,161,163,166,169,172],{"class":34,"line":160},14,[32,162,89],{"class":68},[32,164,165],{"class":72}," (obj ",[32,167,168],{"class":68},"is",[32,170,171],{"class":64}," String",[32,173,101],{"class":72},[32,175,177,179,182],{"class":34,"line":176},15,[32,178,113],{"class":79},[32,180,181],{"class":72},"(obj.length);  ",[32,183,184],{"class":38},"\u002F\u002F obj promoted: Object → String\n",[32,186,188],{"class":34,"line":187},16,[32,189,122],{"class":72},[32,191,193],{"class":34,"line":192},17,[32,194,195],{"class":38},"\u002F\u002F Outside: obj is still Object.\n",[32,197,199],{"class":34,"line":198},18,[32,200,52],{"emptyLinePlaceholder":51},[32,202,204],{"class":34,"line":203},19,[32,205,206],{"class":38},"\u002F\u002F ── 3. Early return promotion ──\n",[32,208,210,212,215,218,220,222],{"class":34,"line":209},20,[32,211,65],{"class":64},[32,213,214],{"class":79}," process",[32,216,217],{"class":72},"(",[32,219,65],{"class":64},[32,221,69],{"class":68},[32,223,224],{"class":72}," input) {\n",[32,226,228,231,234,237,239,242,245,248],{"class":34,"line":227},21,[32,229,230],{"class":68},"  if",[32,232,233],{"class":72}," (input ",[32,235,236],{"class":68},"==",[32,238,98],{"class":64},[32,240,241],{"class":72},") ",[32,243,244],{"class":68},"return",[32,246,247],{"class":153}," 'default'",[32,249,157],{"class":72},[32,251,253],{"class":34,"line":252},22,[32,254,255],{"class":38},"  \u002F\u002F After the null-return, input is promoted to String for the rest of the function.\n",[32,257,259,262,265,268,271],{"class":34,"line":258},23,[32,260,261],{"class":68},"  return",[32,263,264],{"class":72}," input.",[32,266,267],{"class":79},"toUpperCase",[32,269,270],{"class":72},"();  ",[32,272,273],{"class":38},"\u002F\u002F no `!` needed\n",[32,275,277],{"class":34,"line":276},24,[32,278,122],{"class":72},[32,280,282],{"class":34,"line":281},25,[32,283,52],{"emptyLinePlaceholder":51},[32,285,287],{"class":34,"line":286},26,[32,288,289],{"class":38},"\u002F\u002F ── 4. `??` default promotion ──\n",[32,291,293,295,297,300,302,304],{"class":34,"line":292},27,[32,294,65],{"class":64},[32,296,69],{"class":68},[32,298,299],{"class":72}," maybeName ",[32,301,76],{"class":68},[32,303,80],{"class":79},[32,305,83],{"class":72},[32,307,309,311,313,315,317,320,323],{"class":34,"line":308},28,[32,310,65],{"class":64},[32,312,73],{"class":72},[32,314,76],{"class":68},[32,316,299],{"class":72},[32,318,319],{"class":68},"??",[32,321,322],{"class":153}," 'Anonymous'",[32,324,157],{"class":72},[32,326,328],{"class":34,"line":327},29,[32,329,330],{"class":38},"\u002F\u002F `name` is non-nullable String (if maybeName was null, it's 'Anonymous').\n",[32,332,334],{"class":34,"line":333},30,[32,335,52],{"emptyLinePlaceholder":51},[32,337,339],{"class":34,"line":338},31,[32,340,341],{"class":38},"\u002F\u002F ── 5. Definite assignment (late locals) ──\n",[32,343,345,348,350],{"class":34,"line":344},32,[32,346,347],{"class":68},"late",[32,349,171],{"class":64},[32,351,352],{"class":72}," result;\n",[32,354,356,358],{"class":34,"line":355},33,[32,357,89],{"class":68},[32,359,360],{"class":72}," (condition) {\n",[32,362,364,367,369,372],{"class":34,"line":363},34,[32,365,366],{"class":72},"  result ",[32,368,76],{"class":68},[32,370,371],{"class":153}," 'yes'",[32,373,157],{"class":72},[32,375,377,380,383],{"class":34,"line":376},35,[32,378,379],{"class":72},"} ",[32,381,382],{"class":68},"else",[32,384,385],{"class":72}," {\n",[32,387,389,391,393,396],{"class":34,"line":388},36,[32,390,366],{"class":72},[32,392,76],{"class":68},[32,394,395],{"class":153}," 'no'",[32,397,157],{"class":72},[32,399,401],{"class":34,"line":400},37,[32,402,122],{"class":72},[32,404,406,409,412],{"class":34,"line":405},38,[32,407,408],{"class":79},"print",[32,410,411],{"class":72},"(result);  ",[32,413,414],{"class":38},"\u002F\u002F ✓ assigned in all branches — definite assignment analysis\n",[416,417,419],"h3",{"id":418},"promotion-failures-fields-closures-async-gaps","Promotion Failures — Fields, Closures, Async Gaps",[19,421,422],{"language":21},[23,423,425],{"className":25,"code":424,"language":21,"meta":27,"style":27},"\u002F\u002F ── FIELDS: promotion does NOT work on class fields. ──\n\u002F\u002F The field is shared mutable state — another method could set it to null\n\u002F\u002F between the check and the use.\nclass Service {\n  String? _cached;\n\n  void use() {\n    if (_cached != null) {\n      \u002F\u002F print(_cached.length);  \u002F\u002F ✗ _cached is still String?\n    }\n  }\n\n  \u002F\u002F ✓ Fix: copy to a local (immutable snapshot).\n  void useFixed() {\n    final cached = _cached;  \u002F\u002F local — can't be mutated externally\n    if (cached != null) {\n      print(cached.length);  \u002F\u002F ✓ promoted to String\n    }\n  }\n}\n\n\u002F\u002F ── CLOSURES: promotion doesn't cross function boundaries. ──\nvoid closureExample() {\n  String? name = getInput();\n  if (name != null) {\n    final callback = () {\n      \u002F\u002F print(name.length);  \u002F\u002F ✗ not promoted inside the closure\n      \u002F\u002F The closure could execute later, after `name` was set to null.\n    };\n    callback();\n  }\n}\n\n\u002F\u002F ✓ Fix: capture in a final local before the closure.\nvoid closureFixed() {\n  final name = getInput();\n  if (name != null) {\n    final captured = name;  \u002F\u002F final — immutable, promotion-safe\n    final callback = () => print(captured.length);  \u002F\u002F ✓\n    callback();\n  }\n}\n\n\u002F\u002F ── AWAIT GAP: promotion is invalidated after `await`. ──\nFuture\u003Cvoid> awaitExample() async {\n  String? name = getInput();\n  if (name != null) {\n    await Future.delayed(Duration.zero);  \u002F\u002F yields to event loop\n    \u002F\u002F print(name.length);  \u002F\u002F ✗ not promoted after await\n    \u002F\u002F During the await, another async task could set `name = null`.\n  }\n}\n\n\u002F\u002F ✓ Fix: snapshot before await.\nFuture\u003Cvoid> awaitFixed() async {\n  final name = getInput();\n  if (name != null) {\n    final captured = name;\n    await Future.delayed(Duration.zero);\n    print(captured.length);  \u002F\u002F ✓ captured is final, non-nullable\n  }\n}\n",[29,426,427,432,437,442,452,462,466,477,491,499,504,509,513,518,527,543,556,567,571,575,579,583,588,598,612,624,636,644,649,654,661,665,669,673,678,687,700,712,727,751,758,763,768,773,779,804,819,832,858,867,873,878,883,888,894,914,927,940,952,970,981,986],{"__ignoreMap":27},[32,428,429],{"class":34,"line":35},[32,430,431],{"class":38},"\u002F\u002F ── FIELDS: promotion does NOT work on class fields. ──\n",[32,433,434],{"class":34,"line":42},[32,435,436],{"class":38},"\u002F\u002F The field is shared mutable state — another method could set it to null\n",[32,438,439],{"class":34,"line":48},[32,440,441],{"class":38},"\u002F\u002F between the check and the use.\n",[32,443,444,447,450],{"class":34,"line":55},[32,445,446],{"class":68},"class",[32,448,449],{"class":64}," Service",[32,451,385],{"class":72},[32,453,454,457,459],{"class":34,"line":61},[32,455,456],{"class":64},"  String",[32,458,69],{"class":68},[32,460,461],{"class":72}," _cached;\n",[32,463,464],{"class":34,"line":86},[32,465,52],{"emptyLinePlaceholder":51},[32,467,468,471,474],{"class":34,"line":104},[32,469,470],{"class":68},"  void",[32,472,473],{"class":79}," use",[32,475,476],{"class":72},"() {\n",[32,478,479,482,485,487,489],{"class":34,"line":110},[32,480,481],{"class":68},"    if",[32,483,484],{"class":72}," (_cached ",[32,486,95],{"class":68},[32,488,98],{"class":64},[32,490,101],{"class":72},[32,492,493,496],{"class":34,"line":119},[32,494,495],{"class":38},"      \u002F\u002F print(_cached.length);",[32,497,498],{"class":38},"  \u002F\u002F ✗ _cached is still String?\n",[32,500,501],{"class":34,"line":125},[32,502,503],{"class":72},"    }\n",[32,505,506],{"class":34,"line":131},[32,507,508],{"class":72},"  }\n",[32,510,511],{"class":34,"line":136},[32,512,52],{"emptyLinePlaceholder":51},[32,514,515],{"class":34,"line":142},[32,516,517],{"class":38},"  \u002F\u002F ✓ Fix: copy to a local (immutable snapshot).\n",[32,519,520,522,525],{"class":34,"line":160},[32,521,470],{"class":68},[32,523,524],{"class":79}," useFixed",[32,526,476],{"class":72},[32,528,529,532,535,537,540],{"class":34,"line":176},[32,530,531],{"class":68},"    final",[32,533,534],{"class":72}," cached ",[32,536,76],{"class":68},[32,538,539],{"class":72}," _cached;  ",[32,541,542],{"class":38},"\u002F\u002F local — can't be mutated externally\n",[32,544,545,547,550,552,554],{"class":34,"line":187},[32,546,481],{"class":68},[32,548,549],{"class":72}," (cached ",[32,551,95],{"class":68},[32,553,98],{"class":64},[32,555,101],{"class":72},[32,557,558,561,564],{"class":34,"line":192},[32,559,560],{"class":79},"      print",[32,562,563],{"class":72},"(cached.length);  ",[32,565,566],{"class":38},"\u002F\u002F ✓ promoted to String\n",[32,568,569],{"class":34,"line":198},[32,570,503],{"class":72},[32,572,573],{"class":34,"line":203},[32,574,508],{"class":72},[32,576,577],{"class":34,"line":209},[32,578,122],{"class":72},[32,580,581],{"class":34,"line":227},[32,582,52],{"emptyLinePlaceholder":51},[32,584,585],{"class":34,"line":252},[32,586,587],{"class":38},"\u002F\u002F ── CLOSURES: promotion doesn't cross function boundaries. ──\n",[32,589,590,593,596],{"class":34,"line":258},[32,591,592],{"class":68},"void",[32,594,595],{"class":79}," closureExample",[32,597,476],{"class":72},[32,599,600,602,604,606,608,610],{"class":34,"line":276},[32,601,456],{"class":64},[32,603,69],{"class":68},[32,605,73],{"class":72},[32,607,76],{"class":68},[32,609,80],{"class":79},[32,611,83],{"class":72},[32,613,614,616,618,620,622],{"class":34,"line":281},[32,615,230],{"class":68},[32,617,92],{"class":72},[32,619,95],{"class":68},[32,621,98],{"class":64},[32,623,101],{"class":72},[32,625,626,628,631,633],{"class":34,"line":286},[32,627,531],{"class":68},[32,629,630],{"class":72}," callback ",[32,632,76],{"class":68},[32,634,635],{"class":72}," () {\n",[32,637,638,641],{"class":34,"line":292},[32,639,640],{"class":38},"      \u002F\u002F print(name.length);",[32,642,643],{"class":38},"  \u002F\u002F ✗ not promoted inside the closure\n",[32,645,646],{"class":34,"line":308},[32,647,648],{"class":38},"      \u002F\u002F The closure could execute later, after `name` was set to null.\n",[32,650,651],{"class":34,"line":327},[32,652,653],{"class":72},"    };\n",[32,655,656,659],{"class":34,"line":333},[32,657,658],{"class":79},"    callback",[32,660,83],{"class":72},[32,662,663],{"class":34,"line":338},[32,664,508],{"class":72},[32,666,667],{"class":34,"line":344},[32,668,122],{"class":72},[32,670,671],{"class":34,"line":355},[32,672,52],{"emptyLinePlaceholder":51},[32,674,675],{"class":34,"line":363},[32,676,677],{"class":38},"\u002F\u002F ✓ Fix: capture in a final local before the closure.\n",[32,679,680,682,685],{"class":34,"line":376},[32,681,592],{"class":68},[32,683,684],{"class":79}," closureFixed",[32,686,476],{"class":72},[32,688,689,692,694,696,698],{"class":34,"line":388},[32,690,691],{"class":68},"  final",[32,693,73],{"class":72},[32,695,76],{"class":68},[32,697,80],{"class":79},[32,699,83],{"class":72},[32,701,702,704,706,708,710],{"class":34,"line":400},[32,703,230],{"class":68},[32,705,92],{"class":72},[32,707,95],{"class":68},[32,709,98],{"class":64},[32,711,101],{"class":72},[32,713,714,716,719,721,724],{"class":34,"line":405},[32,715,531],{"class":68},[32,717,718],{"class":72}," captured ",[32,720,76],{"class":68},[32,722,723],{"class":72}," name;  ",[32,725,726],{"class":38},"\u002F\u002F final — immutable, promotion-safe\n",[32,728,730,732,734,736,739,742,745,748],{"class":34,"line":729},39,[32,731,531],{"class":68},[32,733,630],{"class":72},[32,735,76],{"class":68},[32,737,738],{"class":72}," () ",[32,740,741],{"class":68},"=>",[32,743,744],{"class":79}," print",[32,746,747],{"class":72},"(captured.length);  ",[32,749,750],{"class":38},"\u002F\u002F ✓\n",[32,752,754,756],{"class":34,"line":753},40,[32,755,658],{"class":79},[32,757,83],{"class":72},[32,759,761],{"class":34,"line":760},41,[32,762,508],{"class":72},[32,764,766],{"class":34,"line":765},42,[32,767,122],{"class":72},[32,769,771],{"class":34,"line":770},43,[32,772,52],{"emptyLinePlaceholder":51},[32,774,776],{"class":34,"line":775},44,[32,777,778],{"class":38},"\u002F\u002F ── AWAIT GAP: promotion is invalidated after `await`. ──\n",[32,780,782,785,788,790,793,796,799,802],{"class":34,"line":781},45,[32,783,784],{"class":64},"Future",[32,786,787],{"class":72},"\u003C",[32,789,592],{"class":68},[32,791,792],{"class":72},"> ",[32,794,795],{"class":79},"awaitExample",[32,797,798],{"class":72},"() ",[32,800,801],{"class":68},"async",[32,803,385],{"class":72},[32,805,807,809,811,813,815,817],{"class":34,"line":806},46,[32,808,456],{"class":64},[32,810,69],{"class":68},[32,812,73],{"class":72},[32,814,76],{"class":68},[32,816,80],{"class":79},[32,818,83],{"class":72},[32,820,822,824,826,828,830],{"class":34,"line":821},47,[32,823,230],{"class":68},[32,825,92],{"class":72},[32,827,95],{"class":68},[32,829,98],{"class":64},[32,831,101],{"class":72},[32,833,835,838,841,844,847,849,852,855],{"class":34,"line":834},48,[32,836,837],{"class":68},"    await",[32,839,840],{"class":64}," Future",[32,842,843],{"class":72},".",[32,845,846],{"class":79},"delayed",[32,848,217],{"class":72},[32,850,851],{"class":64},"Duration",[32,853,854],{"class":72},".zero);  ",[32,856,857],{"class":38},"\u002F\u002F yields to event loop\n",[32,859,861,864],{"class":34,"line":860},49,[32,862,863],{"class":38},"    \u002F\u002F print(name.length);",[32,865,866],{"class":38},"  \u002F\u002F ✗ not promoted after await\n",[32,868,870],{"class":34,"line":869},50,[32,871,872],{"class":38},"    \u002F\u002F During the await, another async task could set `name = null`.\n",[32,874,876],{"class":34,"line":875},51,[32,877,508],{"class":72},[32,879,881],{"class":34,"line":880},52,[32,882,122],{"class":72},[32,884,886],{"class":34,"line":885},53,[32,887,52],{"emptyLinePlaceholder":51},[32,889,891],{"class":34,"line":890},54,[32,892,893],{"class":38},"\u002F\u002F ✓ Fix: snapshot before await.\n",[32,895,897,899,901,903,905,908,910,912],{"class":34,"line":896},55,[32,898,784],{"class":64},[32,900,787],{"class":72},[32,902,592],{"class":68},[32,904,792],{"class":72},[32,906,907],{"class":79},"awaitFixed",[32,909,798],{"class":72},[32,911,801],{"class":68},[32,913,385],{"class":72},[32,915,917,919,921,923,925],{"class":34,"line":916},56,[32,918,691],{"class":68},[32,920,73],{"class":72},[32,922,76],{"class":68},[32,924,80],{"class":79},[32,926,83],{"class":72},[32,928,930,932,934,936,938],{"class":34,"line":929},57,[32,931,230],{"class":68},[32,933,92],{"class":72},[32,935,95],{"class":68},[32,937,98],{"class":64},[32,939,101],{"class":72},[32,941,943,945,947,949],{"class":34,"line":942},58,[32,944,531],{"class":68},[32,946,718],{"class":72},[32,948,76],{"class":68},[32,950,951],{"class":72}," name;\n",[32,953,955,957,959,961,963,965,967],{"class":34,"line":954},59,[32,956,837],{"class":68},[32,958,840],{"class":64},[32,960,843],{"class":72},[32,962,846],{"class":79},[32,964,217],{"class":72},[32,966,851],{"class":64},[32,968,969],{"class":72},".zero);\n",[32,971,973,976,978],{"class":34,"line":972},60,[32,974,975],{"class":79},"    print",[32,977,747],{"class":72},[32,979,980],{"class":38},"\u002F\u002F ✓ captured is final, non-nullable\n",[32,982,984],{"class":34,"line":983},61,[32,985,508],{"class":72},[32,987,989],{"class":34,"line":988},62,[32,990,122],{"class":72},[14,992,994,996],{"id":993},"late-lazy-initialization-initialization-semantics",[29,995,347],{}," — Lazy Initialization & Initialization Semantics",[19,998,999],{"language":21},[23,1000,1002],{"className":25,"code":1001,"language":21,"meta":27,"style":27},"\u002F\u002F `late` tells the compiler: \"this non-nullable variable will be assigned before first read.\"\n\u002F\u002F Without an initializer: you must assign it manually — reading first throws.\n\u002F\u002F With an initializer: runs lazily on first read, caches the result.\n\n\u002F\u002F ── late without initializer — manual assignment ──\nclass Controller {\n  late Database db;  \u002F\u002F will be set in initialize()\n\n  Future\u003Cvoid> initialize(String connStr) async {\n    db = await Database.connect(connStr);\n  }\n\n  Future\u003CList\u003CUser>> getUsers() async {\n    \u002F\u002F If initialize() wasn't called, this throws LateInitializationError.\n    return db.query('SELECT * FROM users');\n  }\n}\n\n\u002F\u002F ── late final with initializer — lazy, cached, runs once ──\nclass AppConfig {\n  \u002F\u002F _loadConfig() runs ONLY on first read of `config`, then caches.\n  \u002F\u002F If `config` is never read, _loadConfig() never runs — zero cost.\n  late final String config = _loadConfig();\n\n  String _loadConfig() {\n    print('Loading config...');  \u002F\u002F runs once\n    return Platform.environment['APP_ENV'] ?? 'development';\n  }\n}\n\n\u002F\u002F ❌ Anti-pattern: late on a field that might not be assigned.\nclass Risky {\n  late String value;  \u002F\u002F if used before assignment → LateInitializationError\n\n  void maybeInit(bool condition) {\n    if (condition) value = 'initialized';\n    \u002F\u002F If condition is false, reading `value` throws.\n  }\n}\n\n\u002F\u002F ✓ Correct: use late final with an initializer (guaranteed safe) or nullable.\nclass Safe {\n  late final String value = _compute();  \u002F\u002F always safe — runs on first read\n  String? maybeValue;  \u002F\u002F nullable — check with `!= null` before use\n\n  String _compute() => 'computed';\n}\n",[29,1003,1004,1009,1014,1019,1023,1028,1037,1051,1055,1080,1100,1104,1108,1134,1139,1158,1162,1166,1170,1175,1184,1189,1194,1213,1217,1225,1240,1263,1267,1271,1275,1280,1289,1301,1305,1320,1334,1339,1343,1347,1351,1356,1365,1386,1398,1402,1417],{"__ignoreMap":27},[32,1005,1006],{"class":34,"line":35},[32,1007,1008],{"class":38},"\u002F\u002F `late` tells the compiler: \"this non-nullable variable will be assigned before first read.\"\n",[32,1010,1011],{"class":34,"line":42},[32,1012,1013],{"class":38},"\u002F\u002F Without an initializer: you must assign it manually — reading first throws.\n",[32,1015,1016],{"class":34,"line":48},[32,1017,1018],{"class":38},"\u002F\u002F With an initializer: runs lazily on first read, caches the result.\n",[32,1020,1021],{"class":34,"line":55},[32,1022,52],{"emptyLinePlaceholder":51},[32,1024,1025],{"class":34,"line":61},[32,1026,1027],{"class":38},"\u002F\u002F ── late without initializer — manual assignment ──\n",[32,1029,1030,1032,1035],{"class":34,"line":86},[32,1031,446],{"class":68},[32,1033,1034],{"class":64}," Controller",[32,1036,385],{"class":72},[32,1038,1039,1042,1045,1048],{"class":34,"line":104},[32,1040,1041],{"class":68},"  late",[32,1043,1044],{"class":64}," Database",[32,1046,1047],{"class":72}," db;  ",[32,1049,1050],{"class":38},"\u002F\u002F will be set in initialize()\n",[32,1052,1053],{"class":34,"line":110},[32,1054,52],{"emptyLinePlaceholder":51},[32,1056,1057,1060,1062,1064,1066,1069,1071,1073,1076,1078],{"class":34,"line":119},[32,1058,1059],{"class":64},"  Future",[32,1061,787],{"class":72},[32,1063,592],{"class":68},[32,1065,792],{"class":72},[32,1067,1068],{"class":79},"initialize",[32,1070,217],{"class":72},[32,1072,65],{"class":64},[32,1074,1075],{"class":72}," connStr) ",[32,1077,801],{"class":68},[32,1079,385],{"class":72},[32,1081,1082,1085,1087,1090,1092,1094,1097],{"class":34,"line":125},[32,1083,1084],{"class":72},"    db ",[32,1086,76],{"class":68},[32,1088,1089],{"class":68}," await",[32,1091,1044],{"class":64},[32,1093,843],{"class":72},[32,1095,1096],{"class":79},"connect",[32,1098,1099],{"class":72},"(connStr);\n",[32,1101,1102],{"class":34,"line":131},[32,1103,508],{"class":72},[32,1105,1106],{"class":34,"line":136},[32,1107,52],{"emptyLinePlaceholder":51},[32,1109,1110,1112,1114,1117,1119,1122,1125,1128,1130,1132],{"class":34,"line":142},[32,1111,1059],{"class":64},[32,1113,787],{"class":72},[32,1115,1116],{"class":64},"List",[32,1118,787],{"class":72},[32,1120,1121],{"class":64},"User",[32,1123,1124],{"class":72},">> ",[32,1126,1127],{"class":79},"getUsers",[32,1129,798],{"class":72},[32,1131,801],{"class":68},[32,1133,385],{"class":72},[32,1135,1136],{"class":34,"line":160},[32,1137,1138],{"class":38},"    \u002F\u002F If initialize() wasn't called, this throws LateInitializationError.\n",[32,1140,1141,1144,1147,1150,1152,1155],{"class":34,"line":176},[32,1142,1143],{"class":68},"    return",[32,1145,1146],{"class":72}," db.",[32,1148,1149],{"class":79},"query",[32,1151,217],{"class":72},[32,1153,1154],{"class":153},"'SELECT * FROM users'",[32,1156,1157],{"class":72},");\n",[32,1159,1160],{"class":34,"line":187},[32,1161,508],{"class":72},[32,1163,1164],{"class":34,"line":192},[32,1165,122],{"class":72},[32,1167,1168],{"class":34,"line":198},[32,1169,52],{"emptyLinePlaceholder":51},[32,1171,1172],{"class":34,"line":203},[32,1173,1174],{"class":38},"\u002F\u002F ── late final with initializer — lazy, cached, runs once ──\n",[32,1176,1177,1179,1182],{"class":34,"line":209},[32,1178,446],{"class":68},[32,1180,1181],{"class":64}," AppConfig",[32,1183,385],{"class":72},[32,1185,1186],{"class":34,"line":227},[32,1187,1188],{"class":38},"  \u002F\u002F _loadConfig() runs ONLY on first read of `config`, then caches.\n",[32,1190,1191],{"class":34,"line":252},[32,1192,1193],{"class":38},"  \u002F\u002F If `config` is never read, _loadConfig() never runs — zero cost.\n",[32,1195,1196,1198,1201,1203,1206,1208,1211],{"class":34,"line":258},[32,1197,1041],{"class":68},[32,1199,1200],{"class":68}," final",[32,1202,171],{"class":64},[32,1204,1205],{"class":72}," config ",[32,1207,76],{"class":68},[32,1209,1210],{"class":79}," _loadConfig",[32,1212,83],{"class":72},[32,1214,1215],{"class":34,"line":276},[32,1216,52],{"emptyLinePlaceholder":51},[32,1218,1219,1221,1223],{"class":34,"line":281},[32,1220,456],{"class":64},[32,1222,1210],{"class":79},[32,1224,476],{"class":72},[32,1226,1227,1229,1231,1234,1237],{"class":34,"line":286},[32,1228,975],{"class":79},[32,1230,217],{"class":72},[32,1232,1233],{"class":153},"'Loading config...'",[32,1235,1236],{"class":72},");  ",[32,1238,1239],{"class":38},"\u002F\u002F runs once\n",[32,1241,1242,1244,1247,1250,1253,1256,1258,1261],{"class":34,"line":292},[32,1243,1143],{"class":68},[32,1245,1246],{"class":64}," Platform",[32,1248,1249],{"class":72},".environment[",[32,1251,1252],{"class":153},"'APP_ENV'",[32,1254,1255],{"class":72},"] ",[32,1257,319],{"class":68},[32,1259,1260],{"class":153}," 'development'",[32,1262,157],{"class":72},[32,1264,1265],{"class":34,"line":308},[32,1266,508],{"class":72},[32,1268,1269],{"class":34,"line":327},[32,1270,122],{"class":72},[32,1272,1273],{"class":34,"line":333},[32,1274,52],{"emptyLinePlaceholder":51},[32,1276,1277],{"class":34,"line":338},[32,1278,1279],{"class":38},"\u002F\u002F ❌ Anti-pattern: late on a field that might not be assigned.\n",[32,1281,1282,1284,1287],{"class":34,"line":344},[32,1283,446],{"class":68},[32,1285,1286],{"class":64}," Risky",[32,1288,385],{"class":72},[32,1290,1291,1293,1295,1298],{"class":34,"line":355},[32,1292,1041],{"class":68},[32,1294,171],{"class":64},[32,1296,1297],{"class":72}," value;  ",[32,1299,1300],{"class":38},"\u002F\u002F if used before assignment → LateInitializationError\n",[32,1302,1303],{"class":34,"line":363},[32,1304,52],{"emptyLinePlaceholder":51},[32,1306,1307,1309,1312,1314,1317],{"class":34,"line":376},[32,1308,470],{"class":68},[32,1310,1311],{"class":79}," maybeInit",[32,1313,217],{"class":72},[32,1315,1316],{"class":64},"bool",[32,1318,1319],{"class":72}," condition) {\n",[32,1321,1322,1324,1327,1329,1332],{"class":34,"line":388},[32,1323,481],{"class":68},[32,1325,1326],{"class":72}," (condition) value ",[32,1328,76],{"class":68},[32,1330,1331],{"class":153}," 'initialized'",[32,1333,157],{"class":72},[32,1335,1336],{"class":34,"line":400},[32,1337,1338],{"class":38},"    \u002F\u002F If condition is false, reading `value` throws.\n",[32,1340,1341],{"class":34,"line":405},[32,1342,508],{"class":72},[32,1344,1345],{"class":34,"line":729},[32,1346,122],{"class":72},[32,1348,1349],{"class":34,"line":753},[32,1350,52],{"emptyLinePlaceholder":51},[32,1352,1353],{"class":34,"line":760},[32,1354,1355],{"class":38},"\u002F\u002F ✓ Correct: use late final with an initializer (guaranteed safe) or nullable.\n",[32,1357,1358,1360,1363],{"class":34,"line":765},[32,1359,446],{"class":68},[32,1361,1362],{"class":64}," Safe",[32,1364,385],{"class":72},[32,1366,1367,1369,1371,1373,1376,1378,1381,1383],{"class":34,"line":770},[32,1368,1041],{"class":68},[32,1370,1200],{"class":68},[32,1372,171],{"class":64},[32,1374,1375],{"class":72}," value ",[32,1377,76],{"class":68},[32,1379,1380],{"class":79}," _compute",[32,1382,270],{"class":72},[32,1384,1385],{"class":38},"\u002F\u002F always safe — runs on first read\n",[32,1387,1388,1390,1392,1395],{"class":34,"line":775},[32,1389,456],{"class":64},[32,1391,69],{"class":68},[32,1393,1394],{"class":72}," maybeValue;  ",[32,1396,1397],{"class":38},"\u002F\u002F nullable — check with `!= null` before use\n",[32,1399,1400],{"class":34,"line":781},[32,1401,52],{"emptyLinePlaceholder":51},[32,1403,1404,1406,1408,1410,1412,1415],{"class":34,"line":806},[32,1405,456],{"class":64},[32,1407,1380],{"class":79},[32,1409,798],{"class":72},[32,1411,741],{"class":68},[32,1413,1414],{"class":153}," 'computed'",[32,1416,157],{"class":72},[32,1418,1419],{"class":34,"line":821},[32,1420,122],{"class":72},[14,1422,1424],{"id":1423},"nullable-collection-algebra","Nullable Collection Algebra",[19,1426,1427],{"language":21},[23,1428,1430],{"className":25,"code":1429,"language":21,"meta":27,"style":27},"\u002F\u002F Four distinct nullability dimensions for collections — choose carefully.\n\n\u002F\u002F 1. List\u003Cint> — non-null list, non-null elements.\nList\u003Cint> a = [1, 2, 3];\n\u002F\u002F a = null;       \u002F\u002F ✗ list can't be null\n\u002F\u002F a.add(null);    \u002F\u002F ✗ elements can't be null\na.add(4);          \u002F\u002F ✓\n\n\u002F\u002F 2. List\u003Cint>? — nullable list, non-null elements.\nList\u003Cint>? b;\nb = null;          \u002F\u002F ✓ list can be null\nb = [1, 2, 3];     \u002F\u002F ✓\n\u002F\u002F b.add(null);    \u002F\u002F ✗ elements can't be null\nb?.add(4);         \u002F\u002F ✓ no-op if b is null (null-aware access)\nprint(b?.length);  \u002F\u002F 3 or null\n\n\u002F\u002F 3. List\u003Cint?> — non-null list, nullable elements.\nList\u003Cint?> c = [1, null, 3];\n\u002F\u002F c = null;       \u002F\u002F ✗ list can't be null\nc.add(null);       \u002F\u002F ✓ elements can be null\nfor (var val in c) {\n  print(val?.abs());  \u002F\u002F null-aware on each element\n}\n\n\u002F\u002F 4. List\u003Cint?>? — both nullable.\nList\u003Cint?>? d;\nd = null;          \u002F\u002F ✓\nd = [1, null, 3];  \u002F\u002F ✓\nd?.add(null);      \u002F\u002F ✓ no-op if d is null\n\u002F\u002F Accessing elements: d?.first → int? or null (double nullable)\n\n\u002F\u002F ❌ Anti-pattern: confusing List\u003Cint>? with List\u003Cint?>.\nint sumList(List\u003Cint>? nums) {\n  if (nums == null) return 0;\n  return nums.fold(0, (a, b) => a + b);  \u002F\u002F b is int (non-null)\n}\n\nint sumNullableElements(List\u003Cint?> nums) {\n  return nums.whereType\u003Cint>().fold(0, (a, b) => a + b);  \u002F\u002F filters out nulls\n}\n",[29,1431,1432,1437,1441,1446,1480,1488,1496,1514,1518,1523,1539,1554,1577,1584,1605,1620,1624,1629,1657,1664,1681,1701,1721,1725,1729,1734,1750,1763,1786,1807,1812,1816,1821,1843,1863,1895,1899,1903,1921,1956],{"__ignoreMap":27},[32,1433,1434],{"class":34,"line":35},[32,1435,1436],{"class":38},"\u002F\u002F Four distinct nullability dimensions for collections — choose carefully.\n",[32,1438,1439],{"class":34,"line":42},[32,1440,52],{"emptyLinePlaceholder":51},[32,1442,1443],{"class":34,"line":48},[32,1444,1445],{"class":38},"\u002F\u002F 1. List\u003Cint> — non-null list, non-null elements.\n",[32,1447,1448,1450,1452,1455,1458,1460,1463,1466,1469,1472,1474,1477],{"class":34,"line":55},[32,1449,1116],{"class":64},[32,1451,787],{"class":72},[32,1453,1454],{"class":64},"int",[32,1456,1457],{"class":72},"> a ",[32,1459,76],{"class":68},[32,1461,1462],{"class":72}," [",[32,1464,1465],{"class":64},"1",[32,1467,1468],{"class":72},", ",[32,1470,1471],{"class":64},"2",[32,1473,1468],{"class":72},[32,1475,1476],{"class":64},"3",[32,1478,1479],{"class":72},"];\n",[32,1481,1482,1485],{"class":34,"line":61},[32,1483,1484],{"class":38},"\u002F\u002F a = null;",[32,1486,1487],{"class":38},"       \u002F\u002F ✗ list can't be null\n",[32,1489,1490,1493],{"class":34,"line":86},[32,1491,1492],{"class":38},"\u002F\u002F a.add(null);",[32,1494,1495],{"class":38},"    \u002F\u002F ✗ elements can't be null\n",[32,1497,1498,1501,1504,1506,1509,1512],{"class":34,"line":104},[32,1499,1500],{"class":72},"a.",[32,1502,1503],{"class":79},"add",[32,1505,217],{"class":72},[32,1507,1508],{"class":64},"4",[32,1510,1511],{"class":72},");          ",[32,1513,750],{"class":38},[32,1515,1516],{"class":34,"line":110},[32,1517,52],{"emptyLinePlaceholder":51},[32,1519,1520],{"class":34,"line":119},[32,1521,1522],{"class":38},"\u002F\u002F 2. List\u003Cint>? — nullable list, non-null elements.\n",[32,1524,1525,1527,1529,1531,1534,1536],{"class":34,"line":125},[32,1526,1116],{"class":64},[32,1528,787],{"class":72},[32,1530,1454],{"class":64},[32,1532,1533],{"class":72},">",[32,1535,69],{"class":68},[32,1537,1538],{"class":72}," b;\n",[32,1540,1541,1544,1546,1548,1551],{"class":34,"line":131},[32,1542,1543],{"class":72},"b ",[32,1545,76],{"class":68},[32,1547,98],{"class":64},[32,1549,1550],{"class":72},";          ",[32,1552,1553],{"class":38},"\u002F\u002F ✓ list can be null\n",[32,1555,1556,1558,1560,1562,1564,1566,1568,1570,1572,1575],{"class":34,"line":136},[32,1557,1543],{"class":72},[32,1559,76],{"class":68},[32,1561,1462],{"class":72},[32,1563,1465],{"class":64},[32,1565,1468],{"class":72},[32,1567,1471],{"class":64},[32,1569,1468],{"class":72},[32,1571,1476],{"class":64},[32,1573,1574],{"class":72},"];     ",[32,1576,750],{"class":38},[32,1578,1579,1582],{"class":34,"line":142},[32,1580,1581],{"class":38},"\u002F\u002F b.add(null);",[32,1583,1495],{"class":38},[32,1585,1586,1589,1591,1593,1595,1597,1599,1602],{"class":34,"line":160},[32,1587,1588],{"class":72},"b",[32,1590,69],{"class":68},[32,1592,843],{"class":72},[32,1594,1503],{"class":79},[32,1596,217],{"class":72},[32,1598,1508],{"class":64},[32,1600,1601],{"class":72},");         ",[32,1603,1604],{"class":38},"\u002F\u002F ✓ no-op if b is null (null-aware access)\n",[32,1606,1607,1609,1612,1614,1617],{"class":34,"line":176},[32,1608,408],{"class":79},[32,1610,1611],{"class":72},"(b",[32,1613,69],{"class":68},[32,1615,1616],{"class":72},".length);  ",[32,1618,1619],{"class":38},"\u002F\u002F 3 or null\n",[32,1621,1622],{"class":34,"line":187},[32,1623,52],{"emptyLinePlaceholder":51},[32,1625,1626],{"class":34,"line":192},[32,1627,1628],{"class":38},"\u002F\u002F 3. List\u003Cint?> — non-null list, nullable elements.\n",[32,1630,1631,1633,1635,1637,1640,1642,1644,1646,1648,1651,1653,1655],{"class":34,"line":198},[32,1632,1116],{"class":64},[32,1634,787],{"class":72},[32,1636,1454],{"class":64},[32,1638,1639],{"class":72},"?> c ",[32,1641,76],{"class":68},[32,1643,1462],{"class":72},[32,1645,1465],{"class":64},[32,1647,1468],{"class":72},[32,1649,1650],{"class":64},"null",[32,1652,1468],{"class":72},[32,1654,1476],{"class":64},[32,1656,1479],{"class":72},[32,1658,1659,1662],{"class":34,"line":203},[32,1660,1661],{"class":38},"\u002F\u002F c = null;",[32,1663,1487],{"class":38},[32,1665,1666,1669,1671,1673,1675,1678],{"class":34,"line":209},[32,1667,1668],{"class":72},"c.",[32,1670,1503],{"class":79},[32,1672,217],{"class":72},[32,1674,1650],{"class":64},[32,1676,1677],{"class":72},");       ",[32,1679,1680],{"class":38},"\u002F\u002F ✓ elements can be null\n",[32,1682,1683,1686,1689,1692,1695,1698],{"class":34,"line":227},[32,1684,1685],{"class":68},"for",[32,1687,1688],{"class":72}," (",[32,1690,1691],{"class":68},"var",[32,1693,1694],{"class":72}," val ",[32,1696,1697],{"class":68},"in",[32,1699,1700],{"class":72}," c) {\n",[32,1702,1703,1705,1708,1710,1712,1715,1718],{"class":34,"line":252},[32,1704,113],{"class":79},[32,1706,1707],{"class":72},"(val",[32,1709,69],{"class":68},[32,1711,843],{"class":72},[32,1713,1714],{"class":79},"abs",[32,1716,1717],{"class":72},"());  ",[32,1719,1720],{"class":38},"\u002F\u002F null-aware on each element\n",[32,1722,1723],{"class":34,"line":258},[32,1724,122],{"class":72},[32,1726,1727],{"class":34,"line":276},[32,1728,52],{"emptyLinePlaceholder":51},[32,1730,1731],{"class":34,"line":281},[32,1732,1733],{"class":38},"\u002F\u002F 4. List\u003Cint?>? — both nullable.\n",[32,1735,1736,1738,1740,1742,1745,1747],{"class":34,"line":286},[32,1737,1116],{"class":64},[32,1739,787],{"class":72},[32,1741,1454],{"class":64},[32,1743,1744],{"class":72},"?>",[32,1746,69],{"class":68},[32,1748,1749],{"class":72}," d;\n",[32,1751,1752,1755,1757,1759,1761],{"class":34,"line":292},[32,1753,1754],{"class":72},"d ",[32,1756,76],{"class":68},[32,1758,98],{"class":64},[32,1760,1550],{"class":72},[32,1762,750],{"class":38},[32,1764,1765,1767,1769,1771,1773,1775,1777,1779,1781,1784],{"class":34,"line":308},[32,1766,1754],{"class":72},[32,1768,76],{"class":68},[32,1770,1462],{"class":72},[32,1772,1465],{"class":64},[32,1774,1468],{"class":72},[32,1776,1650],{"class":64},[32,1778,1468],{"class":72},[32,1780,1476],{"class":64},[32,1782,1783],{"class":72},"];  ",[32,1785,750],{"class":38},[32,1787,1788,1791,1793,1795,1797,1799,1801,1804],{"class":34,"line":327},[32,1789,1790],{"class":72},"d",[32,1792,69],{"class":68},[32,1794,843],{"class":72},[32,1796,1503],{"class":79},[32,1798,217],{"class":72},[32,1800,1650],{"class":64},[32,1802,1803],{"class":72},");      ",[32,1805,1806],{"class":38},"\u002F\u002F ✓ no-op if d is null\n",[32,1808,1809],{"class":34,"line":333},[32,1810,1811],{"class":38},"\u002F\u002F Accessing elements: d?.first → int? or null (double nullable)\n",[32,1813,1814],{"class":34,"line":338},[32,1815,52],{"emptyLinePlaceholder":51},[32,1817,1818],{"class":34,"line":344},[32,1819,1820],{"class":38},"\u002F\u002F ❌ Anti-pattern: confusing List\u003Cint>? with List\u003Cint?>.\n",[32,1822,1823,1825,1828,1830,1832,1834,1836,1838,1840],{"class":34,"line":355},[32,1824,1454],{"class":64},[32,1826,1827],{"class":79}," sumList",[32,1829,217],{"class":72},[32,1831,1116],{"class":64},[32,1833,787],{"class":72},[32,1835,1454],{"class":64},[32,1837,1533],{"class":72},[32,1839,69],{"class":68},[32,1841,1842],{"class":72}," nums) {\n",[32,1844,1845,1847,1850,1852,1854,1856,1858,1861],{"class":34,"line":363},[32,1846,230],{"class":68},[32,1848,1849],{"class":72}," (nums ",[32,1851,236],{"class":68},[32,1853,98],{"class":64},[32,1855,241],{"class":72},[32,1857,244],{"class":68},[32,1859,1860],{"class":64}," 0",[32,1862,157],{"class":72},[32,1864,1865,1867,1870,1873,1875,1878,1881,1883,1886,1889,1892],{"class":34,"line":376},[32,1866,261],{"class":68},[32,1868,1869],{"class":72}," nums.",[32,1871,1872],{"class":79},"fold",[32,1874,217],{"class":72},[32,1876,1877],{"class":64},"0",[32,1879,1880],{"class":72},", (a, b) ",[32,1882,741],{"class":68},[32,1884,1885],{"class":72}," a ",[32,1887,1888],{"class":68},"+",[32,1890,1891],{"class":72}," b);  ",[32,1893,1894],{"class":38},"\u002F\u002F b is int (non-null)\n",[32,1896,1897],{"class":34,"line":388},[32,1898,122],{"class":72},[32,1900,1901],{"class":34,"line":400},[32,1902,52],{"emptyLinePlaceholder":51},[32,1904,1905,1907,1910,1912,1914,1916,1918],{"class":34,"line":405},[32,1906,1454],{"class":64},[32,1908,1909],{"class":79}," sumNullableElements",[32,1911,217],{"class":72},[32,1913,1116],{"class":64},[32,1915,787],{"class":72},[32,1917,1454],{"class":64},[32,1919,1920],{"class":72},"?> nums) {\n",[32,1922,1923,1925,1927,1930,1932,1934,1937,1939,1941,1943,1945,1947,1949,1951,1953],{"class":34,"line":729},[32,1924,261],{"class":68},[32,1926,1869],{"class":72},[32,1928,1929],{"class":79},"whereType",[32,1931,787],{"class":72},[32,1933,1454],{"class":64},[32,1935,1936],{"class":72},">().",[32,1938,1872],{"class":79},[32,1940,217],{"class":72},[32,1942,1877],{"class":64},[32,1944,1880],{"class":72},[32,1946,741],{"class":68},[32,1948,1885],{"class":72},[32,1950,1888],{"class":68},[32,1952,1891],{"class":72},[32,1954,1955],{"class":38},"\u002F\u002F filters out nulls\n",[32,1957,1958],{"class":34,"line":753},[32,1959,122],{"class":72},[14,1961,1963],{"id":1962},"json-interop-safe-parsing-patterns","JSON Interop — Safe Parsing Patterns",[19,1965,1966],{"language":21},[23,1967,1969],{"className":25,"code":1968,"language":21,"meta":27,"style":27},"import 'dart:convert';\n\n\u002F\u002F jsonDecode returns dynamic — cast carefully.\nfinal raw = jsonDecode('{\"name\": \"Alice\", \"age\": 30, \"email\": null}');\n\n\u002F\u002F ❌ Anti-pattern: `as Type` on possibly-null\u002Fmissing values — throws.\nfinal name = raw['name'] as String;       \u002F\u002F ✓ (exists, is String)\n\u002F\u002F final age = raw['age'] as int;          \u002F\u002F ✓ (exists, is int)\n\u002F\u002F final email = raw['email'] as String;   \u002F\u002F ✗ throws: null is not a String\n\u002F\u002F final phone = raw['phone'] as String;   \u002F\u002F ✗ throws: missing key → null\n\n\u002F\u002F ✓ Correct: `as Type?` (nullable cast) then handle null.\nfinal name2 = raw['name'] as String?;         \u002F\u002F String? — null if missing\nfinal age2 = raw['age'] as int?;              \u002F\u002F int? — null if missing\nfinal email = raw['email'] as String?;        \u002F\u002F String? — null (the value is null)\nfinal phone = raw['phone'] as String?;        \u002F\u002F null (key missing)\n\n\u002F\u002F Type-safe parsing with defaults and validation:\nUser parseUser(Map\u003CString, dynamic> json) {\n  final name = json['name'] as String?;\n  if (name == null || name.isEmpty) {\n    throw FormatException('User name is required');\n  }\n  final age = (json['age'] as num?)?.toInt();  \u002F\u002F num? handles int or double\n  return User(name: name, age: age);\n}\n\n\u002F\u002F Deeply nested JSON — chain null-aware operators:\nfinal city = (raw['address'] as Map\u003CString, dynamic>?)?['city'] as String?;\n\u002F\u002F If 'address' is missing or null → city is null (no throw).\n\u002F\u002F If 'city' is missing → city is null.\n",[29,1970,1971,1981,1985,1990,2010,2014,2019,2046,2054,2062,2070,2074,2079,2106,2135,2163,2190,2194,2199,2223,2246,2262,2277,2281,2319,2340,2344,2348,2353,2407,2412],{"__ignoreMap":27},[32,1972,1973,1976,1979],{"class":34,"line":35},[32,1974,1975],{"class":68},"import",[32,1977,1978],{"class":153}," 'dart:convert'",[32,1980,157],{"class":72},[32,1982,1983],{"class":34,"line":42},[32,1984,52],{"emptyLinePlaceholder":51},[32,1986,1987],{"class":34,"line":48},[32,1988,1989],{"class":38},"\u002F\u002F jsonDecode returns dynamic — cast carefully.\n",[32,1991,1992,1995,1998,2000,2003,2005,2008],{"class":34,"line":55},[32,1993,1994],{"class":68},"final",[32,1996,1997],{"class":72}," raw ",[32,1999,76],{"class":68},[32,2001,2002],{"class":79}," jsonDecode",[32,2004,217],{"class":72},[32,2006,2007],{"class":153},"'{\"name\": \"Alice\", \"age\": 30, \"email\": null}'",[32,2009,1157],{"class":72},[32,2011,2012],{"class":34,"line":61},[32,2013,52],{"emptyLinePlaceholder":51},[32,2015,2016],{"class":34,"line":86},[32,2017,2018],{"class":38},"\u002F\u002F ❌ Anti-pattern: `as Type` on possibly-null\u002Fmissing values — throws.\n",[32,2020,2021,2023,2025,2027,2030,2033,2035,2038,2040,2043],{"class":34,"line":104},[32,2022,1994],{"class":68},[32,2024,73],{"class":72},[32,2026,76],{"class":68},[32,2028,2029],{"class":72}," raw[",[32,2031,2032],{"class":153},"'name'",[32,2034,1255],{"class":72},[32,2036,2037],{"class":68},"as",[32,2039,171],{"class":64},[32,2041,2042],{"class":72},";       ",[32,2044,2045],{"class":38},"\u002F\u002F ✓ (exists, is String)\n",[32,2047,2048,2051],{"class":34,"line":110},[32,2049,2050],{"class":38},"\u002F\u002F final age = raw['age'] as int;",[32,2052,2053],{"class":38},"          \u002F\u002F ✓ (exists, is int)\n",[32,2055,2056,2059],{"class":34,"line":119},[32,2057,2058],{"class":38},"\u002F\u002F final email = raw['email'] as String;",[32,2060,2061],{"class":38},"   \u002F\u002F ✗ throws: null is not a String\n",[32,2063,2064,2067],{"class":34,"line":125},[32,2065,2066],{"class":38},"\u002F\u002F final phone = raw['phone'] as String;",[32,2068,2069],{"class":38},"   \u002F\u002F ✗ throws: missing key → null\n",[32,2071,2072],{"class":34,"line":131},[32,2073,52],{"emptyLinePlaceholder":51},[32,2075,2076],{"class":34,"line":136},[32,2077,2078],{"class":38},"\u002F\u002F ✓ Correct: `as Type?` (nullable cast) then handle null.\n",[32,2080,2081,2083,2086,2088,2090,2092,2094,2096,2098,2100,2103],{"class":34,"line":142},[32,2082,1994],{"class":68},[32,2084,2085],{"class":72}," name2 ",[32,2087,76],{"class":68},[32,2089,2029],{"class":72},[32,2091,2032],{"class":153},[32,2093,1255],{"class":72},[32,2095,2037],{"class":68},[32,2097,171],{"class":64},[32,2099,69],{"class":68},[32,2101,2102],{"class":72},";         ",[32,2104,2105],{"class":38},"\u002F\u002F String? — null if missing\n",[32,2107,2108,2110,2113,2115,2117,2120,2122,2124,2127,2129,2132],{"class":34,"line":160},[32,2109,1994],{"class":68},[32,2111,2112],{"class":72}," age2 ",[32,2114,76],{"class":68},[32,2116,2029],{"class":72},[32,2118,2119],{"class":153},"'age'",[32,2121,1255],{"class":72},[32,2123,2037],{"class":68},[32,2125,2126],{"class":64}," int",[32,2128,69],{"class":68},[32,2130,2131],{"class":72},";              ",[32,2133,2134],{"class":38},"\u002F\u002F int? — null if missing\n",[32,2136,2137,2139,2142,2144,2146,2149,2151,2153,2155,2157,2160],{"class":34,"line":176},[32,2138,1994],{"class":68},[32,2140,2141],{"class":72}," email ",[32,2143,76],{"class":68},[32,2145,2029],{"class":72},[32,2147,2148],{"class":153},"'email'",[32,2150,1255],{"class":72},[32,2152,2037],{"class":68},[32,2154,171],{"class":64},[32,2156,69],{"class":68},[32,2158,2159],{"class":72},";        ",[32,2161,2162],{"class":38},"\u002F\u002F String? — null (the value is null)\n",[32,2164,2165,2167,2170,2172,2174,2177,2179,2181,2183,2185,2187],{"class":34,"line":187},[32,2166,1994],{"class":68},[32,2168,2169],{"class":72}," phone ",[32,2171,76],{"class":68},[32,2173,2029],{"class":72},[32,2175,2176],{"class":153},"'phone'",[32,2178,1255],{"class":72},[32,2180,2037],{"class":68},[32,2182,171],{"class":64},[32,2184,69],{"class":68},[32,2186,2159],{"class":72},[32,2188,2189],{"class":38},"\u002F\u002F null (key missing)\n",[32,2191,2192],{"class":34,"line":192},[32,2193,52],{"emptyLinePlaceholder":51},[32,2195,2196],{"class":34,"line":198},[32,2197,2198],{"class":38},"\u002F\u002F Type-safe parsing with defaults and validation:\n",[32,2200,2201,2203,2206,2208,2211,2213,2215,2217,2220],{"class":34,"line":203},[32,2202,1121],{"class":64},[32,2204,2205],{"class":79}," parseUser",[32,2207,217],{"class":72},[32,2209,2210],{"class":64},"Map",[32,2212,787],{"class":72},[32,2214,65],{"class":64},[32,2216,1468],{"class":72},[32,2218,2219],{"class":64},"dynamic",[32,2221,2222],{"class":72},"> json) {\n",[32,2224,2225,2227,2229,2231,2234,2236,2238,2240,2242,2244],{"class":34,"line":209},[32,2226,691],{"class":68},[32,2228,73],{"class":72},[32,2230,76],{"class":68},[32,2232,2233],{"class":72}," json[",[32,2235,2032],{"class":153},[32,2237,1255],{"class":72},[32,2239,2037],{"class":68},[32,2241,171],{"class":64},[32,2243,69],{"class":68},[32,2245,157],{"class":72},[32,2247,2248,2250,2252,2254,2256,2259],{"class":34,"line":227},[32,2249,230],{"class":68},[32,2251,92],{"class":72},[32,2253,236],{"class":68},[32,2255,98],{"class":64},[32,2257,2258],{"class":68}," ||",[32,2260,2261],{"class":72}," name.isEmpty) {\n",[32,2263,2264,2267,2270,2272,2275],{"class":34,"line":252},[32,2265,2266],{"class":68},"    throw",[32,2268,2269],{"class":64}," FormatException",[32,2271,217],{"class":72},[32,2273,2274],{"class":153},"'User name is required'",[32,2276,1157],{"class":72},[32,2278,2279],{"class":34,"line":258},[32,2280,508],{"class":72},[32,2282,2283,2285,2288,2290,2293,2295,2297,2299,2302,2304,2307,2309,2311,2314,2316],{"class":34,"line":276},[32,2284,691],{"class":68},[32,2286,2287],{"class":72}," age ",[32,2289,76],{"class":68},[32,2291,2292],{"class":72}," (json[",[32,2294,2119],{"class":153},[32,2296,1255],{"class":72},[32,2298,2037],{"class":68},[32,2300,2301],{"class":64}," num",[32,2303,69],{"class":68},[32,2305,2306],{"class":72},")",[32,2308,69],{"class":68},[32,2310,843],{"class":72},[32,2312,2313],{"class":79},"toInt",[32,2315,270],{"class":72},[32,2317,2318],{"class":38},"\u002F\u002F num? handles int or double\n",[32,2320,2321,2323,2326,2329,2332,2335,2337],{"class":34,"line":281},[32,2322,261],{"class":68},[32,2324,2325],{"class":64}," User",[32,2327,2328],{"class":72},"(name",[32,2330,2331],{"class":68},":",[32,2333,2334],{"class":72}," name, age",[32,2336,2331],{"class":68},[32,2338,2339],{"class":72}," age);\n",[32,2341,2342],{"class":34,"line":286},[32,2343,122],{"class":72},[32,2345,2346],{"class":34,"line":292},[32,2347,52],{"emptyLinePlaceholder":51},[32,2349,2350],{"class":34,"line":308},[32,2351,2352],{"class":38},"\u002F\u002F Deeply nested JSON — chain null-aware operators:\n",[32,2354,2355,2357,2360,2362,2365,2368,2370,2372,2375,2377,2379,2381,2383,2385,2387,2389,2391,2394,2397,2399,2401,2403,2405],{"class":34,"line":327},[32,2356,1994],{"class":68},[32,2358,2359],{"class":72}," city ",[32,2361,76],{"class":68},[32,2363,2364],{"class":72}," (raw[",[32,2366,2367],{"class":153},"'address'",[32,2369,1255],{"class":72},[32,2371,2037],{"class":68},[32,2373,2374],{"class":64}," Map",[32,2376,787],{"class":72},[32,2378,65],{"class":64},[32,2380,1468],{"class":72},[32,2382,2219],{"class":64},[32,2384,1533],{"class":72},[32,2386,69],{"class":68},[32,2388,2306],{"class":72},[32,2390,69],{"class":68},[32,2392,2393],{"class":72},"[",[32,2395,2396],{"class":153},"'city'",[32,2398,1255],{"class":72},[32,2400,2037],{"class":68},[32,2402,171],{"class":64},[32,2404,69],{"class":68},[32,2406,157],{"class":72},[32,2408,2409],{"class":34,"line":333},[32,2410,2411],{"class":38},"\u002F\u002F If 'address' is missing or null → city is null (no throw).\n",[32,2413,2414],{"class":34,"line":338},[32,2415,2416],{"class":38},"\u002F\u002F If 'city' is missing → city is null.\n",[14,2418,2420,2423,2424,2426],{"id":2419},"object-vs-dynamic-the-safety-boundary",[29,2421,2422],{},"Object?"," vs ",[29,2425,2219],{}," — The Safety Boundary",[19,2428,2429],{"language":21},[23,2430,2432],{"className":25,"code":2431,"language":21,"meta":27,"style":27},"\u002F\u002F ── Object?: nullable, type-safe supertype of ALL types (including Null) ──\n\u002F\u002F Static checking is ON. You must check\u002Fcast to use methods.\nObject? maybeAnything = 'hello';\nmaybeAnything = null;          \u002F\u002F ✓\nmaybeAnything = 42;            \u002F\u002F ✓\n\u002F\u002F maybeAnything.length;       \u002F\u002F ✗ Object? has no `length` — must check type\nif (maybeAnything is String) {\n  print(maybeAnything.length); \u002F\u002F ✓ promoted to String\n}\n\n\u002F\u002F ── dynamic: disables ALL static type checking ──\n\u002F\u002F Any method call compiles — checked at RUNTIME (NoSuchMethodError).\ndynamic dyn = 'hello';\ndyn.length;      \u002F\u002F ✓ compiles, works (String has length)\ndyn = 42;\ndyn.length;      \u002F\u002F ✓ compiles, throws NoSuchMethodError at runtime (int has no length)\ndyn.nonExistent; \u002F\u002F ✓ compiles, throws at runtime\n\n\u002F\u002F ❌ Anti-pattern: using `dynamic` for \"I don't know the type.\"\n\u002F\u002F It defeats the entire type system. Bugs surface at runtime, not compile time.\n\n\u002F\u002F ✓ Correct: use `Object?` for \"any value including null.\"\n\u002F\u002F Use `Object` for \"any non-null value.\"\n\u002F\u002F Use `dynamic` ONLY at interop boundaries (JSON, JS interop) — never in APIs.\n",[29,2433,2434,2439,2444,2459,2472,2486,2494,2507,2516,2520,2524,2529,2534,2547,2555,2566,2573,2581,2585,2590,2595,2599,2604,2609],{"__ignoreMap":27},[32,2435,2436],{"class":34,"line":35},[32,2437,2438],{"class":38},"\u002F\u002F ── Object?: nullable, type-safe supertype of ALL types (including Null) ──\n",[32,2440,2441],{"class":34,"line":42},[32,2442,2443],{"class":38},"\u002F\u002F Static checking is ON. You must check\u002Fcast to use methods.\n",[32,2445,2446,2448,2450,2453,2455,2457],{"class":34,"line":48},[32,2447,145],{"class":64},[32,2449,69],{"class":68},[32,2451,2452],{"class":72}," maybeAnything ",[32,2454,76],{"class":68},[32,2456,154],{"class":153},[32,2458,157],{"class":72},[32,2460,2461,2464,2466,2468,2470],{"class":34,"line":55},[32,2462,2463],{"class":72},"maybeAnything ",[32,2465,76],{"class":68},[32,2467,98],{"class":64},[32,2469,1550],{"class":72},[32,2471,750],{"class":38},[32,2473,2474,2476,2478,2481,2484],{"class":34,"line":61},[32,2475,2463],{"class":72},[32,2477,76],{"class":68},[32,2479,2480],{"class":64}," 42",[32,2482,2483],{"class":72},";            ",[32,2485,750],{"class":38},[32,2487,2488,2491],{"class":34,"line":86},[32,2489,2490],{"class":38},"\u002F\u002F maybeAnything.length;",[32,2492,2493],{"class":38},"       \u002F\u002F ✗ Object? has no `length` — must check type\n",[32,2495,2496,2498,2501,2503,2505],{"class":34,"line":104},[32,2497,89],{"class":68},[32,2499,2500],{"class":72}," (maybeAnything ",[32,2502,168],{"class":68},[32,2504,171],{"class":64},[32,2506,101],{"class":72},[32,2508,2509,2511,2514],{"class":34,"line":110},[32,2510,113],{"class":79},[32,2512,2513],{"class":72},"(maybeAnything.length); ",[32,2515,566],{"class":38},[32,2517,2518],{"class":34,"line":119},[32,2519,122],{"class":72},[32,2521,2522],{"class":34,"line":125},[32,2523,52],{"emptyLinePlaceholder":51},[32,2525,2526],{"class":34,"line":131},[32,2527,2528],{"class":38},"\u002F\u002F ── dynamic: disables ALL static type checking ──\n",[32,2530,2531],{"class":34,"line":136},[32,2532,2533],{"class":38},"\u002F\u002F Any method call compiles — checked at RUNTIME (NoSuchMethodError).\n",[32,2535,2536,2538,2541,2543,2545],{"class":34,"line":142},[32,2537,2219],{"class":64},[32,2539,2540],{"class":72}," dyn ",[32,2542,76],{"class":68},[32,2544,154],{"class":153},[32,2546,157],{"class":72},[32,2548,2549,2552],{"class":34,"line":160},[32,2550,2551],{"class":72},"dyn.length;      ",[32,2553,2554],{"class":38},"\u002F\u002F ✓ compiles, works (String has length)\n",[32,2556,2557,2560,2562,2564],{"class":34,"line":176},[32,2558,2559],{"class":72},"dyn ",[32,2561,76],{"class":68},[32,2563,2480],{"class":64},[32,2565,157],{"class":72},[32,2567,2568,2570],{"class":34,"line":187},[32,2569,2551],{"class":72},[32,2571,2572],{"class":38},"\u002F\u002F ✓ compiles, throws NoSuchMethodError at runtime (int has no length)\n",[32,2574,2575,2578],{"class":34,"line":192},[32,2576,2577],{"class":72},"dyn.nonExistent; ",[32,2579,2580],{"class":38},"\u002F\u002F ✓ compiles, throws at runtime\n",[32,2582,2583],{"class":34,"line":198},[32,2584,52],{"emptyLinePlaceholder":51},[32,2586,2587],{"class":34,"line":203},[32,2588,2589],{"class":38},"\u002F\u002F ❌ Anti-pattern: using `dynamic` for \"I don't know the type.\"\n",[32,2591,2592],{"class":34,"line":209},[32,2593,2594],{"class":38},"\u002F\u002F It defeats the entire type system. Bugs surface at runtime, not compile time.\n",[32,2596,2597],{"class":34,"line":227},[32,2598,52],{"emptyLinePlaceholder":51},[32,2600,2601],{"class":34,"line":252},[32,2602,2603],{"class":38},"\u002F\u002F ✓ Correct: use `Object?` for \"any value including null.\"\n",[32,2605,2606],{"class":34,"line":258},[32,2607,2608],{"class":38},"\u002F\u002F Use `Object` for \"any non-null value.\"\n",[32,2610,2611],{"class":34,"line":276},[32,2612,2613],{"class":38},"\u002F\u002F Use `dynamic` ONLY at interop boundaries (JSON, JS interop) — never in APIs.\n",[14,2615,2617],{"id":2616},"tips-tricks","💡 Tips & Tricks",[2619,2620,2621,2651,2667,2678,2700],"ul",{},[2622,2623,2624,2628,2629,2632,2633,2636,2637,2640,2641,2643,2644,2646,2647,2650],"li",{},[2625,2626,2627],"strong",{},"Idiom",": ",[29,2630,2631],{},"if (x != null)"," for promotion (preferred over ",[29,2634,2635],{},"!",") — promotes ",[29,2638,2639],{},"x"," to non-null inside the block, no runtime throw risk. ",[29,2642,2635],{}," is a runtime assertion that crashes if wrong. Use ",[29,2645,2635],{}," only on framework invariants (Flutter widget properties after ",[29,2648,2649],{},"initState",").",[2622,2652,2653,2628,2655,2658,2659,2662,2663,2666],{},[2625,2654,2627],{},[29,2656,2657],{},"??="," for lazy cache initialization — ",[29,2660,2661],{},"cache[key] ??= compute(key)"," runs ",[29,2664,2665],{},"compute"," only on cache miss. The right side is evaluated only if the left is null. Zero overhead if cached.",[2622,2668,2669,2628,2671,2674,2675,2677],{},[2625,2670,2627],{},[29,2672,2673],{},"late final x = expensive()"," for lazy fields — the initializer runs once on first read, then caches. If ",[29,2676,2639],{}," is never read, the initializer never runs. Use for expensive fields that may not always be needed.",[2622,2679,2680,2628,2682,2685,2686,2689,2690,2692,2693,2696,2697,2699],{},[2625,2681,2627],{},[29,2683,2684],{},"as Type?"," then handle null for JSON — ",[29,2687,2688],{},"(json['key'] as String?)"," returns ",[29,2691,1650],{}," if missing or null. ",[29,2694,2695],{},"json['key'] as String"," throws if missing. Use ",[29,2698,2684],{}," for all JSON field access.",[2622,2701,2702,2628,2704,2706,2707,2709,2710,2712,2713,2715,2716,2718],{},[2625,2703,2627],{},[29,2705,2422],{}," over ",[29,2708,2219],{}," for \"any value\" — ",[29,2711,2422],{}," preserves static type checking (you must check\u002Fcast), ",[29,2714,2219],{}," disables it. Use ",[29,2717,2219],{}," only for JSON\u002FJS interop, never in public APIs.",[14,2720,2722],{"id":2721},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[2619,2724,2725,2748,2763,2777,2792,2811,2828,2849,2873,2892],{},[2622,2726,2727,2628,2736,2739,2740,1468,2742,2744,2745,843],{},[2625,2728,2729,2731,2732,2735],{},[29,2730,2635],{}," throws ",[29,2733,2734],{},"TypeError"," at runtime",[29,2737,2738],{},"null!"," crashes. It defeats null safety. Use only when certain (framework invariants). Prefer ",[29,2741,2631],{},[29,2743,319],{},", or ",[29,2746,2747],{},"?.",[2622,2749,2750,2628,2753,2756,2757,2759,2760,843],{},[2625,2751,2752],{},"Fields don't promote",[29,2754,2755],{},"if (this.x != null) { x.length }"," — ",[29,2758,2639],{}," is still nullable. Copy to a local: ",[29,2761,2762],{},"final x = this.x; if (x != null) { x.length; }",[2622,2764,2765,2628,2768,2756,2771,2773,2774,2776],{},[2625,2766,2767],{},"Promotion doesn't cross closures",[29,2769,2770],{},"if (x != null) { () => x.length; }",[29,2772,2639],{}," isn't promoted inside the closure. Capture in a ",[29,2775,1994],{}," local before the closure.",[2622,2778,2779,2785,2786,2788,2789,843],{},[2625,2780,2781,2782],{},"Promotion is invalidated after ",[29,2783,2784],{},"await",": after ",[29,2787,2784],{},", a nullable local may have been set to null. Snapshot before await: ",[29,2790,2791],{},"final captured = x; if (captured != null) { await ...; captured.length; }",[2622,2793,2794,2628,2799,2802,2803,2806,2807,2810],{},[2625,2795,2796,2798],{},[29,2797,347],{}," throws on early read",[29,2800,2801],{},"late int x; print(x)"," → ",[29,2804,2805],{},"LateInitializationError",". Use ",[29,2808,2809],{},"late final x = initializer"," (safe lazy) or make the variable nullable.",[2622,2812,2813,2628,2822,2824,2825,2827],{},[2625,2814,2815,2818,2819],{},[29,2816,2817],{},"List\u003Cint?>"," ≠ ",[29,2820,2821],{},"List\u003Cint>?",[29,2823,2817],{}," is a non-null list with nullable elements. ",[29,2826,2821],{}," is a nullable list with non-null elements. Choose based on what can be null.",[2622,2829,2830,2628,2835,2838,2839,2842,2843,2845,2846,2848],{},[2625,2831,2832,2834],{},[29,2833,2219],{}," is nullable",[29,2836,2837],{},"dynamic x = null"," is valid. ",[29,2840,2841],{},"x.foo()"," compiles (throws at runtime). Don't confuse ",[29,2844,2219],{}," with ",[29,2847,145],{}," (non-nullable).",[2622,2850,2851,2628,2856,2859,2860,2862,2863,2859,2866,2869,2870,2872],{},[2625,2852,2853,2855],{},[29,2854,319],{}," only checks for null",[29,2857,2858],{},"0 ?? 'default'"," is ",[29,2861,1877],{}," (0 isn't null). ",[29,2864,2865],{},"false ?? true",[29,2867,2868],{},"false",". Only ",[29,2871,1650],{}," triggers the fallback — no falsy coercion.",[2622,2874,2875,2628,2882,2838,2885,2888,2889,2891],{},[2625,2876,2877,2879,2880],{},[29,2878,2422],{}," accepts everything including ",[29,2881,1650],{},[29,2883,2884],{},"Object? x = null",[29,2886,2887],{},"Object x = null"," is a compile error. Use ",[29,2890,2422],{}," for \"any value, including null.\"",[2622,2893,2894,2897,2898,2901],{},[2625,2895,2896],{},"Sound null safety is mandatory in Dart 3",": there's no opt-out. All dependencies must be null-safe. Run ",[29,2899,2900],{},"dart pub outdated --mode=nullity"," to check legacy deps.",[14,2903,2905],{"id":2904},"spot-the-bug","🧠 Spot the Bug",[2907,2908,2909],"p",{},"A developer checks a nullable field in an async method, then uses it after an await:",[19,2911,2912],{"language":21},[23,2913,2915],{"className":25,"code":2914,"language":21,"meta":27,"style":27},"class Repo {\n  String? _cached;\n\n  Future\u003Cvoid> refresh() async {\n    if (_cached != null) {\n      await _fetchUpdate();\n      print(_cached.length);  \u002F\u002F ✗ compile error: _cached is String?\n    }\n  }\n\n  Future\u003Cvoid> _fetchUpdate() async { \u002F* ... *\u002F }\n}\n",[29,2916,2917,2926,2934,2938,2957,2969,2979,2989,2993,2997,3001,3027],{"__ignoreMap":27},[32,2918,2919,2921,2924],{"class":34,"line":35},[32,2920,446],{"class":68},[32,2922,2923],{"class":64}," Repo",[32,2925,385],{"class":72},[32,2927,2928,2930,2932],{"class":34,"line":42},[32,2929,456],{"class":64},[32,2931,69],{"class":68},[32,2933,461],{"class":72},[32,2935,2936],{"class":34,"line":48},[32,2937,52],{"emptyLinePlaceholder":51},[32,2939,2940,2942,2944,2946,2948,2951,2953,2955],{"class":34,"line":55},[32,2941,1059],{"class":64},[32,2943,787],{"class":72},[32,2945,592],{"class":68},[32,2947,792],{"class":72},[32,2949,2950],{"class":79},"refresh",[32,2952,798],{"class":72},[32,2954,801],{"class":68},[32,2956,385],{"class":72},[32,2958,2959,2961,2963,2965,2967],{"class":34,"line":61},[32,2960,481],{"class":68},[32,2962,484],{"class":72},[32,2964,95],{"class":68},[32,2966,98],{"class":64},[32,2968,101],{"class":72},[32,2970,2971,2974,2977],{"class":34,"line":86},[32,2972,2973],{"class":68},"      await",[32,2975,2976],{"class":79}," _fetchUpdate",[32,2978,83],{"class":72},[32,2980,2981,2983,2986],{"class":34,"line":104},[32,2982,560],{"class":79},[32,2984,2985],{"class":72},"(_cached.length);  ",[32,2987,2988],{"class":38},"\u002F\u002F ✗ compile error: _cached is String?\n",[32,2990,2991],{"class":34,"line":110},[32,2992,503],{"class":72},[32,2994,2995],{"class":34,"line":119},[32,2996,508],{"class":72},[32,2998,2999],{"class":34,"line":125},[32,3000,52],{"emptyLinePlaceholder":51},[32,3002,3003,3005,3007,3009,3011,3014,3016,3018,3021,3024],{"class":34,"line":131},[32,3004,1059],{"class":64},[32,3006,787],{"class":72},[32,3008,592],{"class":68},[32,3010,792],{"class":72},[32,3012,3013],{"class":79},"_fetchUpdate",[32,3015,798],{"class":72},[32,3017,801],{"class":68},[32,3019,3020],{"class":72}," { ",[32,3022,3023],{"class":38},"\u002F* ... *\u002F",[32,3025,3026],{"class":72}," }\n",[32,3028,3029],{"class":34,"line":136},[32,3030,122],{"class":72},[2907,3032,3033],{},"Two problems — what are they?",[3035,3036,3037,3041,3078,3081,3209],"details",{},[3038,3039,3040],"summary",{},"Answer",[3042,3043,3044,3063],"ol",{},[2622,3045,3046,2628,3048,3051,3052,3055,3056,3058,3059,3062],{},[2625,3047,2752],{},[29,3049,3050],{},"if (_cached != null)"," doesn't promote ",[29,3053,3054],{},"_cached"," to ",[29,3057,65],{}," because it's a class field — another method could set ",[29,3060,3061],{},"_cached = null"," between the check and the use.",[2622,3064,3065,3070,3071,3074,3075,3077],{},[2625,3066,3067,3068],{},"Promotion invalidated after ",[29,3069,2784],{},": even if promotion worked for fields, the ",[29,3072,3073],{},"await _fetchUpdate()"," yields to the event loop. During that gap, another async task could set ",[29,3076,3061],{},". The compiler knows this and refuses to promote.",[2907,3079,3080],{},"The fix — snapshot to a final local before the await:",[23,3082,3084],{"className":25,"code":3083,"language":21,"meta":27,"style":27},"class Repo {\n  String? _cached;\n\n  Future\u003Cvoid> refresh() async {\n    final cached = _cached;  \u002F\u002F immutable local snapshot\n    if (cached != null) {\n      await _fetchUpdate();\n      \u002F\u002F `cached` is still the non-null snapshot — safe to use after await.\n      print(cached.length);  \u002F\u002F ✓ promoted to String, safe after await\n    }\n  }\n\n  Future\u003Cvoid> _fetchUpdate() async { \u002F* ... *\u002F }\n}\n",[29,3085,3086,3094,3102,3106,3124,3137,3149,3157,3162,3171,3175,3179,3183,3205],{"__ignoreMap":27},[32,3087,3088,3090,3092],{"class":34,"line":35},[32,3089,446],{"class":68},[32,3091,2923],{"class":64},[32,3093,385],{"class":72},[32,3095,3096,3098,3100],{"class":34,"line":42},[32,3097,456],{"class":64},[32,3099,69],{"class":68},[32,3101,461],{"class":72},[32,3103,3104],{"class":34,"line":48},[32,3105,52],{"emptyLinePlaceholder":51},[32,3107,3108,3110,3112,3114,3116,3118,3120,3122],{"class":34,"line":55},[32,3109,1059],{"class":64},[32,3111,787],{"class":72},[32,3113,592],{"class":68},[32,3115,792],{"class":72},[32,3117,2950],{"class":79},[32,3119,798],{"class":72},[32,3121,801],{"class":68},[32,3123,385],{"class":72},[32,3125,3126,3128,3130,3132,3134],{"class":34,"line":61},[32,3127,531],{"class":68},[32,3129,534],{"class":72},[32,3131,76],{"class":68},[32,3133,539],{"class":72},[32,3135,3136],{"class":38},"\u002F\u002F immutable local snapshot\n",[32,3138,3139,3141,3143,3145,3147],{"class":34,"line":86},[32,3140,481],{"class":68},[32,3142,549],{"class":72},[32,3144,95],{"class":68},[32,3146,98],{"class":64},[32,3148,101],{"class":72},[32,3150,3151,3153,3155],{"class":34,"line":104},[32,3152,2973],{"class":68},[32,3154,2976],{"class":79},[32,3156,83],{"class":72},[32,3158,3159],{"class":34,"line":110},[32,3160,3161],{"class":38},"      \u002F\u002F `cached` is still the non-null snapshot — safe to use after await.\n",[32,3163,3164,3166,3168],{"class":34,"line":119},[32,3165,560],{"class":79},[32,3167,563],{"class":72},[32,3169,3170],{"class":38},"\u002F\u002F ✓ promoted to String, safe after await\n",[32,3172,3173],{"class":34,"line":125},[32,3174,503],{"class":72},[32,3176,3177],{"class":34,"line":131},[32,3178,508],{"class":72},[32,3180,3181],{"class":34,"line":136},[32,3182,52],{"emptyLinePlaceholder":51},[32,3184,3185,3187,3189,3191,3193,3195,3197,3199,3201,3203],{"class":34,"line":142},[32,3186,1059],{"class":64},[32,3188,787],{"class":72},[32,3190,592],{"class":68},[32,3192,792],{"class":72},[32,3194,3013],{"class":79},[32,3196,798],{"class":72},[32,3198,801],{"class":68},[32,3200,3020],{"class":72},[32,3202,3023],{"class":38},[32,3204,3026],{"class":72},[32,3206,3207],{"class":34,"line":160},[32,3208,122],{"class":72},[2907,3210,3211,3212,2859,3215,3217,3218,3220,3221,843],{},"The local ",[29,3213,3214],{},"cached",[29,3216,1994],{}," — it can't be reassigned by anyone, so the promotion holds across the ",[29,3219,2784],{}," gap. The snapshot captures the value at check time, immune to concurrent mutation of ",[29,3222,3054],{},[3224,3225,3226],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}",{"title":27,"searchDepth":42,"depth":42,"links":3228},[3229,3232,3234,3235,3236,3238,3239,3240],{"id":16,"depth":42,"text":17,"children":3230},[3231],{"id":418,"depth":48,"text":419},{"id":993,"depth":42,"text":3233},"late — Lazy Initialization & Initialization Semantics",{"id":1423,"depth":42,"text":1424},{"id":1962,"depth":42,"text":1963},{"id":2419,"depth":42,"text":3237},"Object? vs dynamic — The Safety Boundary",{"id":2616,"depth":42,"text":2617},{"id":2721,"depth":42,"text":2722},{"id":2904,"depth":42,"text":2905},"Deep-dive into Dart's sound null safety system, type promotion rules across closures\u002Fasync\u002Ffields, late initialization semantics, nullable collection algebra, and JSON interop patterns. Code-first engineering reference.","md",{},"\u002Fdart\u002F08-null-safety",{"title":5,"description":3241},"dart\u002F08-null-safety","Q5jGQQ9JVrlgg7-MKh87dJh3MrhfCnqWU8XHSWTuN4A",1789924647723]