本文提出了任何贡献者在参加开发时必须遵守的规则。
请参考 https://ruby-china.org/topics/15737 的详细 Commit Message 风格指导。
# 好👍
feat: create bottom menu for comment views (close #12)
update: some translation texts
remove: useless codes
# 坏👎
new menu
fix
update
clean
旦夕引入了 Hilt 作为 ViewModel 的依赖管理工具,
因为 Hilt 可以把依赖作用域限制在 ViewModel
或者 Application
的级别,不同的 ViewModel
可以共享不同的全局变量。
同时 Hilt 是编译时注入,不会影响运行时性能。
// 使用 Annotation 设置依赖
@Singleton
class MyGlobalClass @Inject constructor() {
// ...
}
// 在其他地方注入依赖
@ViewModelScoped
class MyViewModelScopedClass @Inject constructor(
myGlobalClass: MyGlobalClass
) {
// ...
}
// 在 `ViewModel` 中注入依赖
@HiltViewModel
class MyViewModel @inject constructor(
myGlobalClass: MyGlobalClass
) : ViewModel() {
// 获取 Context
@ApplicationContext lateinit var context: Context
// ...
}
fun foo(): String = "result"
fun foo(): String {
val res = "result"
return res
}
例外
除非必须有这么做的理由。例如,紧凑之后会使得代码层级过多,变得难以阅读。或者需要方法保持开放,随时进行修改或插桩测试。
suspend fun getDataFromNetwork(): String {
val result: String? = innerCall()
// 会在 result == null 时抛出异常
return requireNotNull(result) { "Get null data from network." }
}
suspend fun getDataFromNetwork(): String? {
val result: String? = innerCall()
return result
}
例外
除非对于 API 而言,返回 null 本身就是一种可用的信息。例如,获取发帖列表的 API 可能返回 null,表示没有发帖。
suspend fun getDataFromNetwork() = withContext(Dispatchers.IO){
networkRequest()
}
fun getDataFromNetwork(): String {
return networkRequest()
}
suspend fun getDataFromNetwork(): String {
// 一大堆会产生各种不可恢复的异常的请求代码……
// 另一大堆会产生各种不可恢复的异常的响应解析代码……
return result
}
suspend fun getDataFromNetwork(): String {
try {
// 一大堆会产生各种不可恢复的异常的请求代码……
} catch (e: HTTPException) {
throw ExactTypeException()
}
try {
// 另一大堆会产生各种不可恢复的异常的响应解析代码……
} catch (e: FormatException) {
throw AnotherExactTypeException()
}
return result
}
suspend fun getDataFromNetwork(): String {
try {
// 一大堆会产生各种不可恢复的异常的请求代码……
} catch (e: Exception) {
println(e)
}
try {
// 另一大堆会产生各种不可恢复的异常的响应解析代码……
} catch (e: Exception) {
println(e)
}
return result
}
例外
除非该异常是可恢复的。可恢复的定义是:即便不执行本身的主要逻辑第二次(例如:重新请求网络),也可以返回正确的结果。
fun clickRefreshData() {
showProgressBar()
viewModelScope.launch {
try {
// 一大堆会产生各种不可恢复的异常的请求代码……
} catch (e: Exception) {
showErrorTips(e)
}
}
}
fun clickRefreshData() {
showProgressBar()
viewModelScope.launch {
// 一大堆会产生各种不可恢复的异常的请求代码……
}
}
// ViewModel 中
data class MyUiState(
val clicked: Boolean = false
)
private val _uiState = MutableStateFlow(MyUiState())
val uiState: StateFlow<MyUiState> = _uiState.asStateFlow()
fun onClick() {
// 更新状态
_uiState.update { it.copy(clicked = true) }
}
// --------------------------------
// Compose 中
@Composable
fun MyComposable(viewModel: MyViewModel) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Button(onClick = { viewModel.onClick() }) {
Text("Click me")
}
}
补充
对于较为复杂的页面状态,可以根据各 State 更新的频繁与否,创建多个
UiState
。避免由于某个变量频繁变更,而不得不在每一个变更时复制所有状态。
视图层(如 Compose
)理应承担所有视图任务,如显示动画、显示对话框、显示上下文菜单、跳转到新页面等等。其他层,尤其是 ViewModel
,不应当执行任何有关方法。
例外
在自定义的视图-控制器一体类(如
Feature
)中,navigate
是可容忍的。
旦夕是面向多类型账户系统的,各账户系统可以独立登录登出,因此需要用独立的数据类型存储。UISInfo
是只针对 UIS 账号密码的数据类型,OTJWTToken
是只针对 FDUHole 凭证的数据类型。
另外,需要区分账户「凭证」和账户「信息」,前者仅包含登录所需的信息,可以本地存储;后者包含所有信息,应该在登录后或者启动应用时从后端获取。
# 好👍
登录复旦 UIS 账户
使用树洞账号登录旦课
树洞登录
无法连接至复旦 UIS 服务器
# 坏👎
登录账户
使用旦夕账号登录旦课
树洞登录
无法连接至服务器