Moving away from handlers and into coroutines.

This commit is contained in:
Vitor Pamplona
2023-01-19 08:41:48 -05:00
parent 9d52180550
commit 8771584deb
10 changed files with 116 additions and 92 deletions

View File

@@ -13,6 +13,7 @@ import java.util.Collections
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import nostr.postr.events.Event import nostr.postr.events.Event
@@ -99,17 +100,18 @@ class Note(val idHex: String) {
val live: NoteLiveData = NoteLiveData(this) val live: NoteLiveData = NoteLiveData(this)
// Refreshes observers in batches. // Refreshes observers in batches.
val filterHandler = Handler(Looper.getMainLooper()) val scope = CoroutineScope(Job() + Dispatchers.Main)
var handlerWaiting = false var handlerWaiting = false
@Synchronized @Synchronized
fun invalidateData() { fun invalidateData() {
if (handlerWaiting) return if (handlerWaiting) return
handlerWaiting = true handlerWaiting = true
filterHandler.postDelayed({ scope.launch {
delay(100)
live.refresh() live.refresh()
handlerWaiting = false handlerWaiting = false
}, 100) }
} }
} }
@@ -120,19 +122,13 @@ class NoteLiveData(val note: Note): LiveData<NoteState>(NoteState(note)) {
override fun onActive() { override fun onActive() {
super.onActive() super.onActive()
val scope = CoroutineScope(Job() + Dispatchers.Main)
scope.launch {
NostrSingleEventDataSource.add(note.idHex) NostrSingleEventDataSource.add(note.idHex)
} }
}
override fun onInactive() { override fun onInactive() {
super.onInactive() super.onInactive()
val scope = CoroutineScope(Job() + Dispatchers.Main)
scope.launch {
NostrSingleEventDataSource.remove(note.idHex) NostrSingleEventDataSource.remove(note.idHex)
} }
}
} }
class NoteState(val note: Note) class NoteState(val note: Note)

View File

@@ -22,7 +22,7 @@ object UrlCachedPreviewer {
return return
} }
val scope = CoroutineScope(Job() + Dispatchers.Main) val scope = CoroutineScope(Job() + Dispatchers.IO)
scope.launch { scope.launch {
BahaUrlPreview(url, object : IUrlPreviewCallback { BahaUrlPreview(url, object : IUrlPreviewCallback {
override fun onComplete(urlInfo: UrlInfoItem) { override fun onComplete(urlInfo: UrlInfoItem) {

View File

@@ -11,6 +11,7 @@ import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import nostr.postr.events.ContactListEvent import nostr.postr.events.ContactListEvent
@@ -112,18 +113,18 @@ class User(val pubkey: ByteArray) {
val live: UserLiveData = UserLiveData(this) val live: UserLiveData = UserLiveData(this)
// Refreshes observers in batches. // Refreshes observers in batches.
val filterHandler = Handler(Looper.getMainLooper()) val scope = CoroutineScope(Job() + Dispatchers.Main)
var handlerWaiting = false var handlerWaiting = false
@Synchronized @Synchronized
fun invalidateData() { fun invalidateData() {
if (handlerWaiting) return if (handlerWaiting) return
handlerWaiting = true handlerWaiting = true
filterHandler.postDelayed({ scope.launch {
println("User Refresh") delay(100)
live.refresh() live.refresh()
handlerWaiting = false handlerWaiting = false
}, 100) }
} }
} }
@@ -153,25 +154,19 @@ class UserMetadata {
} }
class UserLiveData(val user: User): LiveData<UserState>(UserState(user)) { class UserLiveData(val user: User): LiveData<UserState>(UserState(user)) {
val scope = CoroutineScope(Job() + Dispatchers.Main)
fun refresh() { fun refresh() {
postValue(UserState(user)) postValue(UserState(user))
} }
override fun onActive() { override fun onActive() {
super.onActive() super.onActive()
scope.launch {
NostrSingleUserDataSource.add(user.pubkeyHex) NostrSingleUserDataSource.add(user.pubkeyHex)
} }
}
override fun onInactive() { override fun onInactive() {
super.onInactive() super.onInactive()
scope.launch {
NostrSingleUserDataSource.remove(user.pubkeyHex) NostrSingleUserDataSource.remove(user.pubkeyHex)
} }
}
} }
class UserState(val user: User) class UserState(val user: User)

View File

@@ -41,7 +41,7 @@ object NostrAccountDataSource: NostrDataSource<Note>("AccountData") {
return JsonFilter( return JsonFilter(
kinds = listOf(MetadataEvent.kind), kinds = listOf(MetadataEvent.kind),
authors = listOf(account.userProfile().pubkeyHex), authors = listOf(account.userProfile().pubkeyHex),
limit = 1 limit = 3
) )
} }

View File

@@ -20,6 +20,7 @@ import kotlin.time.measureTimedValue
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import nostr.postr.events.ContactListEvent import nostr.postr.events.ContactListEvent
import nostr.postr.events.DeletionEvent import nostr.postr.events.DeletionEvent
@@ -118,7 +119,7 @@ abstract class NostrDataSource<T>(val debugName: String) {
val returningList = feed().take(100) val returningList = feed().take(100)
// prepare previews // prepare previews
val scope = CoroutineScope(Job() + Dispatchers.Main) val scope = CoroutineScope(Job() + Dispatchers.IO)
scope.launch { scope.launch {
loadPreviews(returningList) loadPreviews(returningList)
} }
@@ -147,17 +148,18 @@ abstract class NostrDataSource<T>(val debugName: String) {
channelIds.remove(channel.id) channelIds.remove(channel.id)
} }
val filterHandler = Handler(Looper.getMainLooper()) val scope = CoroutineScope(Job() + Dispatchers.IO)
var handlerWaiting = false var handlerWaiting = false
@Synchronized @Synchronized
fun invalidateFilters() { fun invalidateFilters() {
if (handlerWaiting) return if (handlerWaiting) return
handlerWaiting = true handlerWaiting = true
filterHandler.postDelayed({ scope.launch {
delay(200)
resetFilters() resetFilters()
handlerWaiting = false handlerWaiting = false
}, 200) }
} }
fun resetFilters() { fun resetFilters() {

View File

@@ -16,7 +16,7 @@ object NostrSingleUserDataSource: NostrDataSource<Note>("SingleUserFeed") {
JsonFilter( JsonFilter(
kinds = listOf(MetadataEvent.kind), kinds = listOf(MetadataEvent.kind),
authors = listOf(it.substring(0, 8)), authors = listOf(it.substring(0, 8)),
limit = 1 limit = 10
) )
} }
} }

View File

@@ -49,7 +49,10 @@ fun DrawerContent(navController: NavHostController,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
accountStateViewModel: AccountStateViewModel) { accountStateViewModel: AccountStateViewModel) {
val accountUserState by accountViewModel.userLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
val accountUserState by account.userProfile().live.observeAsState()
val accountUser = accountUserState?.user val accountUser = accountUserState?.user
Surface( Surface(
@@ -64,14 +67,18 @@ fun DrawerContent(navController: NavHostController,
model = banner, model = banner,
contentDescription = "Profile Image", contentDescription = "Profile Image",
contentScale = ContentScale.FillWidth, contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth().height(150.dp) modifier = Modifier
.fillMaxWidth()
.height(150.dp)
) )
} else { } else {
Image( Image(
painter = painterResource(R.drawable.profile_banner), painter = painterResource(R.drawable.profile_banner),
contentDescription = "Profile Banner", contentDescription = "Profile Banner",
contentScale = ContentScale.FillWidth, contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth().height(150.dp) modifier = Modifier
.fillMaxWidth()
.height(150.dp)
) )
} }
@@ -106,12 +113,15 @@ fun DrawerContent(navController: NavHostController,
fun ProfileContent(accountUser: User?, modifier: Modifier = Modifier, scaffoldState: ScaffoldState, navController: NavController) { fun ProfileContent(accountUser: User?, modifier: Modifier = Modifier, scaffoldState: ScaffoldState, navController: NavController) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
println("AAA " + accountUser?.profilePicture())
Column(modifier = modifier) { Column(modifier = modifier) {
AsyncImage( AsyncImage(
model = accountUser?.profilePicture() ?: "https://robohash.org/ohno.png", model = accountUser?.profilePicture() ?: "https://robohash.org/ohno.png",
contentDescription = "Profile Image", contentDescription = "Profile Image",
modifier = Modifier modifier = Modifier
.width(100.dp).height(100.dp) .width(100.dp)
.height(100.dp)
.clip(shape = CircleShape) .clip(shape = CircleShape)
.border(3.dp, MaterialTheme.colors.background, CircleShape) .border(3.dp, MaterialTheme.colors.background, CircleShape)
.background(MaterialTheme.colors.background) .background(MaterialTheme.colors.background)
@@ -205,7 +215,8 @@ fun NavigationRow(navController: NavHostController, scaffoldState: ScaffoldState
}) })
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth() modifier = Modifier
.fillMaxWidth()
.padding(vertical = 15.dp, horizontal = 25.dp), .padding(vertical = 15.dp, horizontal = 25.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {

View File

@@ -14,6 +14,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
@@ -26,8 +27,13 @@ class CardFeedViewModel(val dataSource: NostrDataSource<Note>): ViewModel() {
private var lastNotes: List<Note>? = null private var lastNotes: List<Note>? = null
fun refresh() { fun refresh() {
val scope = CoroutineScope(Job() + Dispatchers.Main) val scope = CoroutineScope(Job() + Dispatchers.IO)
scope.launch { scope.launch {
refreshSuspended()
}
}
private fun refreshSuspended() {
val notes = dataSource.loadTop() val notes = dataSource.loadTop()
val lastNotesCopy = lastNotes val lastNotesCopy = lastNotes
@@ -45,7 +51,6 @@ class CardFeedViewModel(val dataSource: NostrDataSource<Note>): ViewModel() {
updateFeed(cards) updateFeed(cards)
} }
} }
}
private fun convertToCard(notes: List<Note>): List<Card> { private fun convertToCard(notes: List<Note>): List<Card> {
val reactionsPerEvent = mutableMapOf<Note, MutableList<Note>>() val reactionsPerEvent = mutableMapOf<Note, MutableList<Note>>()
@@ -76,31 +81,28 @@ class CardFeedViewModel(val dataSource: NostrDataSource<Note>): ViewModel() {
} }
fun updateFeed(notes: List<Card>) { fun updateFeed(notes: List<Card>) {
val scope = CoroutineScope(Job() + Dispatchers.Main)
scope.launch {
if (notes.isEmpty()) { if (notes.isEmpty()) {
_feedContent.update { CardFeedState.Empty } _feedContent.update { CardFeedState.Empty }
} else { } else {
_feedContent.update { CardFeedState.Loaded(notes) } _feedContent.update { CardFeedState.Loaded(notes) }
} }
} }
fun refreshCurrentList() {
val state = feedContent.value
if (state is CardFeedState.Loaded) {
_feedContent.update { CardFeedState.Loaded(state.feed) }
}
} }
val filterHandler = Handler(Looper.getMainLooper()) val scope = CoroutineScope(Job() + Dispatchers.IO)
var handlerWaiting = false var handlerWaiting = false
@Synchronized @Synchronized
fun invalidateData() { fun invalidateData() {
if (handlerWaiting) return if (handlerWaiting) return
handlerWaiting = true handlerWaiting = true
filterHandler.postDelayed({ scope.launch {
delay(100)
refresh() refresh()
handlerWaiting = false handlerWaiting = false
}, 100) }
} }
private val cacheListener: (LocalCacheState) -> Unit = { private val cacheListener: (LocalCacheState) -> Unit = {

View File

@@ -23,6 +23,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
@@ -42,8 +43,13 @@ abstract class FeedViewModel(val dataSource: NostrDataSource<Note>): ViewModel()
val feedContent = _feedContent.asStateFlow() val feedContent = _feedContent.asStateFlow()
fun refresh() { fun refresh() {
val scope = CoroutineScope(Job() + Dispatchers.Main) val scope = CoroutineScope(Job() + Dispatchers.IO)
scope.launch { scope.launch {
refreshSuspended()
}
}
private fun refreshSuspended() {
val notes = dataSource.loadTop() val notes = dataSource.loadTop()
val oldNotesState = feedContent.value val oldNotesState = feedContent.value
@@ -55,15 +61,17 @@ abstract class FeedViewModel(val dataSource: NostrDataSource<Note>): ViewModel()
updateFeed(notes) updateFeed(notes)
} }
} }
}
fun updateFeed(notes: List<Note>) { fun updateFeed(notes: List<Note>) {
val scope = CoroutineScope(Job() + Dispatchers.Main)
scope.launch {
if (notes.isEmpty()) { if (notes.isEmpty()) {
_feedContent.update { FeedState.Empty } _feedContent.update { FeedState.Empty }
} else { } else {
_feedContent.update { FeedState.Loaded(notes) } _feedContent.update { FeedState.Loaded(notes) }
} }
} }
}
fun refreshCurrentList() { fun refreshCurrentList() {
val state = feedContent.value val state = feedContent.value
@@ -72,17 +80,18 @@ abstract class FeedViewModel(val dataSource: NostrDataSource<Note>): ViewModel()
} }
} }
val filterHandler = Handler(Looper.getMainLooper()) val scope = CoroutineScope(Job() + Dispatchers.IO)
var handlerWaiting = false var handlerWaiting = false
@Synchronized @Synchronized
fun invalidateData() { fun invalidateData() {
if (handlerWaiting) return if (handlerWaiting) return
handlerWaiting = true handlerWaiting = true
filterHandler.postDelayed({ scope.launch {
delay(100)
refresh() refresh()
handlerWaiting = false handlerWaiting = false
}, 100) }
} }
private val cacheListener: (LocalCacheState) -> Unit = { private val cacheListener: (LocalCacheState) -> Unit = {

View File

@@ -14,6 +14,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
@@ -32,8 +33,13 @@ open class UserFeedViewModel(val dataSource: NostrDataSource<User>): ViewModel()
val feedContent = _feedContent.asStateFlow() val feedContent = _feedContent.asStateFlow()
fun refresh() { fun refresh() {
val scope = CoroutineScope(Job() + Dispatchers.Main) val scope = CoroutineScope(Job() + Dispatchers.IO)
scope.launch { scope.launch {
refreshSuspended()
}
}
private fun refreshSuspended() {
val notes = dataSource.loadTop() val notes = dataSource.loadTop()
val oldNotesState = feedContent.value val oldNotesState = feedContent.value
@@ -45,15 +51,17 @@ open class UserFeedViewModel(val dataSource: NostrDataSource<User>): ViewModel()
updateFeed(notes) updateFeed(notes)
} }
} }
}
fun updateFeed(notes: List<User>) { fun updateFeed(notes: List<User>) {
val scope = CoroutineScope(Job() + Dispatchers.Main)
scope.launch {
if (notes.isEmpty()) { if (notes.isEmpty()) {
_feedContent.update { UserFeedState.Empty } _feedContent.update { UserFeedState.Empty }
} else { } else {
_feedContent.update { UserFeedState.Loaded(notes) } _feedContent.update { UserFeedState.Loaded(notes) }
} }
} }
}
fun refreshCurrentList() { fun refreshCurrentList() {
val state = feedContent.value val state = feedContent.value
@@ -62,17 +70,18 @@ open class UserFeedViewModel(val dataSource: NostrDataSource<User>): ViewModel()
} }
} }
val filterHandler = Handler(Looper.getMainLooper()) val scope = CoroutineScope(Job() + Dispatchers.IO)
var handlerWaiting = false var handlerWaiting = false
@Synchronized @Synchronized
fun invalidateData() { fun invalidateData() {
if (handlerWaiting) return if (handlerWaiting) return
handlerWaiting = true handlerWaiting = true
filterHandler.postDelayed({ scope.launch {
delay(100)
refresh() refresh()
handlerWaiting = false handlerWaiting = false
}, 100) }
} }
private val cacheListener: (LocalCacheState) -> Unit = { private val cacheListener: (LocalCacheState) -> Unit = {