Retry connection; get rid of Flow

This commit is contained in:
Philipp Heckel 2021-10-26 12:23:41 -04:00
parent 12d194b8c4
commit 0c26703c78
3 changed files with 81 additions and 88 deletions

View file

@ -14,55 +14,5 @@ import java.net.URL
data class Event(val name: String = "", val data: JsonObject = JsonObject()) data class Event(val name: String = "", val data: JsonObject = JsonObject())
class NtfyApi(context: Context) { class NtfyApi(context: Context) {
private val gson = GsonBuilder().create()
fun createEventsFlow(url: String): Flow<Event> = flow {
coroutineScope {
val conn = getStreamConnection(url)
println("2222222222222")
val input = conn.inputStream.bufferedReader()
try {
conn.connect()
var event = Event()
while (isActive) {
val line = input.readLine()
println("PHIL: " + line)
when {
line == null -> {
this.cancel(CancellationException("EOF"))
break
}
line.startsWith("event:") -> {
event = event.copy(name = line.substring(6).trim())
}
line.startsWith("data:") -> {
val data = line.substring(5).trim()
try {
event = event.copy(data = gson.fromJson(data, JsonObject::class.java))
} catch (e: JsonSyntaxException) {
// Nothing
}
}
line.isEmpty() -> {
emit(event)
event = Event()
}
}
}
} catch (e: IOException) {
println("PHIL: " + e.message)
this.cancel(CancellationException("Network Problem", e))
} finally {
conn.disconnect()
input.close()
}
}
}
private suspend fun getStreamConnection(url: String): HttpURLConnection =
withContext(Dispatchers.IO) {
return@withContext (URL(url).openConnection() as HttpURLConnection).also {
it.doInput = true
}
}
} }

View file

@ -28,9 +28,11 @@ import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.lifecycle.asLiveData
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException
import io.heckel.ntfy.R import io.heckel.ntfy.R
import io.heckel.ntfy.add.AddTopicActivity import io.heckel.ntfy.add.AddTopicActivity
import io.heckel.ntfy.add.TOPIC_URL import io.heckel.ntfy.add.TOPIC_URL
@ -38,15 +40,17 @@ import io.heckel.ntfy.data.Event
import io.heckel.ntfy.data.NtfyApi import io.heckel.ntfy.data.NtfyApi
import io.heckel.ntfy.data.Topic import io.heckel.ntfy.data.Topic
import io.heckel.ntfy.detail.TopicDetailActivity import io.heckel.ntfy.detail.TopicDetailActivity
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.*
import kotlinx.coroutines.launch import java.io.IOException
import kotlinx.coroutines.withContext import java.net.HttpURLConnection
import java.net.URL
import kotlin.random.Random import kotlin.random.Random
const val TOPIC_ID = "topic id" const val TOPIC_ID = "topic id"
class TopicsListActivity : AppCompatActivity() { class TopicsListActivity : AppCompatActivity() {
private val api = NtfyApi(this) private val api = NtfyApi(this)
private val jobs = mutableMapOf<Long, Job>()
private val newTopicActivityRequestCode = 1 private val newTopicActivityRequestCode = 1
private val topicsListViewModel by viewModels<TopicsListViewModel> { private val topicsListViewModel by viewModels<TopicsListViewModel> {
TopicsListViewModelFactory(this) TopicsListViewModelFactory(this)
@ -74,32 +78,6 @@ class TopicsListActivity : AppCompatActivity() {
createNotificationChannel() createNotificationChannel()
} }
private fun startFlow(url: String) {
api.createEventsFlow(url).asLiveData(Dispatchers.IO).observe(this) { event ->
this.lifecycleScope.launch(Dispatchers.Main) {
withContext(Dispatchers.IO) {
handleEvent(event)
}
}
}
}
private fun handleEvent(event: Event) {
if (event.data.isJsonNull || !event.data.has("message")) {
return
}
println("PHIL EVENT: " + event.data)
val channelId = getString(R.string.notification_channel_id)
val notification = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ntfy)
.setContentTitle("ntfy")
.setContentText(event.data.get("message").asString)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build()
with(NotificationManagerCompat.from(this)) {
notify(Random.nextInt(), notification)
}
}
/* Opens TopicDetailActivity when RecyclerView item is clicked. */ /* Opens TopicDetailActivity when RecyclerView item is clicked. */
private fun adapterOnClick(topic: Topic) { private fun adapterOnClick(topic: Topic) {
val intent = Intent(this, TopicDetailActivity()::class.java) val intent = Intent(this, TopicDetailActivity()::class.java)
@ -119,9 +97,12 @@ class TopicsListActivity : AppCompatActivity() {
/* Inserts topic into viewModel. */ /* Inserts topic into viewModel. */
if (requestCode == newTopicActivityRequestCode && resultCode == Activity.RESULT_OK) { if (requestCode == newTopicActivityRequestCode && resultCode == Activity.RESULT_OK) {
intentData?.let { data -> intentData?.let { data ->
val topicId = Random.nextLong()
val topicUrl = data.getStringExtra(TOPIC_URL) ?: return val topicUrl = data.getStringExtra(TOPIC_URL) ?: return
startFlow(topicUrl) val topic = Topic(topicId, topicUrl)
topicsListViewModel.insertTopic(topicUrl)
jobs[topicId] = startListening(topicUrl)
topicsListViewModel.add(topic)
} }
} }
} }
@ -144,4 +125,70 @@ class TopicsListActivity : AppCompatActivity() {
} }
} }
private val gson = GsonBuilder().create()
fun startListening(url: String): Job {
return this.lifecycleScope.launch(Dispatchers.IO) {
while (isActive) {
println("connecting ...")
val conn = (URL(url).openConnection() as HttpURLConnection).also {
it.doInput = true
}
val input = conn.inputStream.bufferedReader()
try {
conn.connect()
var event = Event()
while (isActive) {
val line = input.readLine()
println("PHIL: " + line)
when {
line == null -> {
println("line is null")
break
}
line.startsWith("event:") -> {
event = event.copy(name = line.substring(6).trim())
}
line.startsWith("data:") -> {
val data = line.substring(5).trim()
try {
event = event.copy(data = gson.fromJson(data, JsonObject::class.java))
} catch (e: JsonSyntaxException) {
// Nothing
}
}
line.isEmpty() -> {
handleEvent(event)
event = Event()
}
}
}
} catch (e: IOException) {
println("PHIL: " + e.message)
} finally {
conn.disconnect()
input.close()
}
println("connection died")
delay(5000)
}
}
}
private fun handleEvent(event: Event) {
if (event.data.isJsonNull || !event.data.has("message")) {
return
}
println("PHIL EVENT: " + event.data)
val channelId = getString(R.string.notification_channel_id)
val notification = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ntfy)
.setContentTitle("ntfy")
.setContentText(event.data.get("message").asString)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build()
with(NotificationManagerCompat.from(this)) {
notify(Random.nextInt(), notification)
}
}
} }

View file

@ -27,12 +27,8 @@ import kotlin.random.Random
class TopicsListViewModel(val dataSource: DataSource) : ViewModel() { class TopicsListViewModel(val dataSource: DataSource) : ViewModel() {
val topics: LiveData<List<Topic>> = dataSource.getTopicList() val topics: LiveData<List<Topic>> = dataSource.getTopicList()
fun insertTopic(topicUrl: String) { fun add(topic: Topic) {
val newTopic = Topic( dataSource.addTopic(topic)
Random.nextLong(),
topicUrl
)
dataSource.addTopic(newTopic)
} }
} }