Live Note

Remain optimistic

Flutter web 在 iOS 18.2 版本上只能用两指滑动滚动

issue 地址: [iOS 18.2][Web] Scrolling is broken in browsers (Safari, Chrome)

flutter 版本为 3.25.x, 低于 iOS 18.2 的设备上, 可以单指滚动

原因

Safari 在 iOS 18.2 添加了 getCoalescedEvents()支持, 这个方法用于合并多个 touch 事件,有助于提高性能.
但是 api 是不完整的(与其他浏览器内核实现不符, 缺少了 pointerId 和 target 等返回值), 在 flutter 3.27.1 修改了 binding 部分的内容, 用于兼容: [web] Work around wrong pointerId in coalesced events in iOS Safari 18.2

解决方案

把 flutter 升级到 3.27.1 版本, 或者使用 flutter channel dev 切换到 dev 分支, 然后 flutter upgrade 升级到最新 beta 版本.
也可以将 pr 合并到本地 flutter engine, 直接构建 flutter web 项目.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import System.Environment
import System.Directory
import System.IO
import Data.List

main = do
(command:args) <- getArgs -- get the command
let (Just action) = lookup command dispatch -- find the command
action args -- do something

add :: [String] -> IO ()
add [fileName, todoItem] = appendFile fileName (todoItem ++ "\n") -- add the todoItem

view :: [String] -> IO ()
view [fileName] = do -- match the filename
contents <- readFile fileName -- read the file
let todoTasks = lines contents -- get the contents
numberedTasks = zipWith (\n line -> show n ++ ": " ++ line) [0..] todoTasks
putStrLn $ unlines numberedTasks -- show the items

remove :: [String] -> IO ()
remove [fileName, numberString] = do -- get the filename and the line number
handle <- openFile fileName ReadMode
(tempName, tempHandle) <- openTempFile "." "temp" -- create a temp file
contents <- hGetContents handle -- get contents from the handle
let number = read numberString -- read the String to Number
todoTasks = lines contents
newTodoTasks = delete (todoTasks !! number) todoTasks -- remove the line of the number
hPutStr tempHandle $ unlines newTodoTasks -- putStr into the tempfile
hClose handle -- close handles
hClose tempHandle
removeFile fileName
renameFile tempName fileName

dispatch :: [(String, [String] -> IO ())]
dispatch = [
("add", add),
("view", view),
("remove", remove)
]

-- runhaskell arags-test.hs view newData.txt
-- runhaskell args-test.hs add newData.txt "Say Hello"
-- runhaskell args-test.hs remove newData.txt 4

使用 HOC 进行代码复用

假设存在下面两个组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React from "react"

class ButtonCounter extends React.Component {
constructor(props) {
super(props)

this.state = {
count: 0,
}
}

increase = () => {
let { step } = this.props

this.setState((pre) => {
return {
count: pre.count + step,
}
})
}

render() {
let { count } = this.state

return <button onClick={this.increase}>click {count} times</button>
}
}

export default ButtonCounter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React from "react"

class ButtonCounter extends React.Component {
constructor(props) {
super(props)

this.state = {
count: 0,
}
}

increase = () => {
let { step } = this.props

this.setState((pre) => {
return {
count: pre.count + step,
}
})
}

render() {
let { count } = this.state

return <div onMouseLeave={this.increase}>click {count} times</div>
}
}

export default ButtonCounter

使用 HOC 进行改造

HOC 通过传入的组件,返回一个新的组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from "react"

const withCounter = (OriginalComponent) => {
class newComponent extends React.Component {
render() {
return <OriginalComponent />
}
}

return newComponent
}

export default withCounter
Read more »

Iterator 概念

Iterator 是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构,只要部署 Iterator 接口,就可以完成遍历操作。
Iterator 的主要作用:为数据结构提供统一的、简便的访问接口;使得数据结构的成员能够按照某种次序排列;供 for…of 消费。
遍历过程如下:

  1. 创建一个指针对象,指向当前数据结构的起始位置。
  2. 第一次调用指针对象的 next 方法,将指针指向数据结构的第一个成员。
  3. 第二次调用 next 方法,指向第二个成员。
  4. 不断调用 next 方法,直到指针指向数据结构的结束位置。

每次调用 next 方法都会返回数据结构当前成员的信息,返回一个包含 value 的 done 两个属性的对象。value 属性是当前成员的值,done 属性是一个布尔值,表示遍历是否结束。
模拟 next 方法返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var it = makeIterator(['a', 'b'])
it.next() // { value : 'a', done : false }
it.next() // { value : 'b', done : false }
it.next() // { value : undefined, done : true }
function makeIterator(array) {
var nextIndex = 0
return {
next: function () {
return nextIndex < array.length
? { value: array[nextIndex++], done: false }
: { value: undefined, done: true }
},
}
}

遍历器与所遍历的数据结构实际上是分开的,完全可以写出没有对应数据结构的遍历器对象,或者用遍历器对象模拟出数据结构。无限运行的遍历器对象的例子:

1
2
3
4
5
6
7
8
9
10
11
12
var it = idMaker()
it.next().value // 0
it.next().value // 1
//...
function idMaker() {
var index = 0
return {
next: function () {
return { value: index++, done: false }
},
}
}
Read more »

在使用 Android dev 开发 flutter 应用时,控制台会输出 GPU debug 日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[+1001 ms] D/EGL_emulation( 9434): app_time_stats: avg=1001.31ms min=1001.31ms max=1001.31ms count=1
[+1989 ms] D/EGL_emulation( 9434): app_time_stats: avg=994.95ms min=992.16ms max=997.75ms count=2
[+1996 ms] D/EGL_emulation( 9434): app_time_stats: avg=998.40ms min=994.74ms max=1002.06ms count=2
[+2005 ms] D/EGL_emulation( 9434): app_time_stats: avg=1002.85ms min=990.68ms max=1015.03ms count=2
[+1983 ms] D/EGL_emulation( 9434): app_time_stats: avg=991.95ms min=987.00ms max=996.89ms count=2
[+1016 ms] D/EGL_emulation( 9434): app_time_stats: avg=1015.37ms min=1015.37ms max=1015.37ms count=1
[+1002 ms] D/EGL_emulation( 9434): app_time_stats: avg=1002.23ms min=1002.23ms max=1002.23ms count=1
[+1002 ms] D/EGL_emulation( 9434): app_time_stats: avg=1003.33ms min=1003.33ms max=1003.33ms count=1
[+1008 ms] D/EGL_emulation( 9434): app_time_stats: avg=1005.76ms min=1005.76ms max=1005.76ms count=1
[+1997 ms] D/EGL_emulation( 9434): app_time_stats: avg=999.30ms min=994.23ms max=1004.37ms count=2
[+1998 ms] D/EGL_emulation( 9434): app_time_stats: avg=998.66ms min=998.60ms max=998.71ms count=2
[+2000 ms] D/EGL_emulation( 9434): app_time_stats: avg=1000.03ms min=998.15ms max=1001.91ms count=2
[+1998 ms] D/EGL_emulation( 9434): app_time_stats: avg=999.53ms min=999.27ms max=999.79ms count=2
[+1990 ms] D/EGL_emulation( 9434): app_time_stats: avg=995.32ms min=993.38ms max=997.25ms count=2
[+1001 ms] D/EGL_emulation( 9434): app_time_stats: avg=1001.47ms min=1001.47ms max=1001.47ms count=1
[+1001 ms] D/EGL_emulation( 9434): app_time_stats: avg=1001.27ms min=1001.27ms max=1001.27ms count=1
[+1004 ms] D/EGL_emulation( 9434): app_time_stats: avg=1004.90ms min=1004.90ms max=1004.90ms count=1
[+1005 ms] D/EGL_emulation( 9434): app_time_stats: avg=1005.88ms min=1005.88ms max=1005.88ms count=1
[+1979 ms] D/EGL_emulation( 9434): app_time_stats: avg=989.67ms min=979.52ms max=999.82ms count=2

这些日志并不影响应用的运行,但是会占用控制台的空间,可以通过以下方式忽略掉:

1
flutter run --dart-define=ENV=dev --verbose | grep -v "EGL_emulation"