From 70a30d23ec7f74a783a42ff02923eeb7f73cbf1c Mon Sep 17 00:00:00 2001 From: zhxnlai Date: Thu, 17 Jan 2019 17:50:17 -0800 Subject: [PATCH] Support lifecycle service --- README.md | 4 +- demo/build.gradle | 1 + demo/src/main/AndroidManifest.xml | 12 +- .../com/tinder/app/root/view/DemoActivity.kt | 8 +- .../view/ChatRoomServiceFragment.kt | 72 ++++++++ .../tinder/service/ChatRoomSocketIoService.kt | 160 ++++++++++++++++++ .../res/layout/fragment_chatroom_service.xml | 23 +++ dependencies.gradle | 4 +- gradle.properties | 2 +- .../lifecycle/android/AndroidLifecycle.kt | 14 ++ .../android/LifecycleOwnerStartedLifecycle.kt | 39 +++++ 11 files changed, 328 insertions(+), 11 deletions(-) create mode 100644 demo/src/main/java/com/tinder/app/socketio/chatroomservice/view/ChatRoomServiceFragment.kt create mode 100644 demo/src/main/java/com/tinder/service/ChatRoomSocketIoService.kt create mode 100644 demo/src/main/res/layout/fragment_chatroom_service.xml create mode 100644 scarlet-lifecycle-android/src/main/java/com/tinder/scarlet/lifecycle/android/LifecycleOwnerStartedLifecycle.kt diff --git a/README.md b/README.md index de8dec04..3105d8b6 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ While we are working on Bintray support, Scarlet is available via [JitPack][jitp com.github.tinder.scarlet scarlet - 0.2.2-alpha4 + 0.2.3-alpha1 ``` @@ -98,7 +98,7 @@ repositories { maven { url "https://jitpack.io" } } -implementation 'com.github.tinder.scarlet:scarlet:$0.2.2-alpha4' +implementation 'com.github.tinder.scarlet:scarlet:$0.2.3-alpha1' ``` ### Plug-in Roadmap diff --git a/demo/build.gradle b/demo/build.gradle index 335017f0..5efaaa30 100755 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -51,6 +51,7 @@ dependencies { implementation rootProject.ext.multiDex implementation rootProject.ext.constraintLayout implementation rootProject.ext.liveDateReactiveSterams + implementation rootProject.ext.lifecycleExtensions implementation rootProject.ext.rxBinding implementation rootProject.ext.rxJava implementation rootProject.ext.rxAndroid diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index b6cadabd..e44001f0 100755 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -11,7 +11,7 @@ @@ -28,6 +28,12 @@ + + + - + \ No newline at end of file diff --git a/demo/src/main/java/com/tinder/app/root/view/DemoActivity.kt b/demo/src/main/java/com/tinder/app/root/view/DemoActivity.kt index b6eb3dd9..6d231fa5 100755 --- a/demo/src/main/java/com/tinder/app/root/view/DemoActivity.kt +++ b/demo/src/main/java/com/tinder/app/root/view/DemoActivity.kt @@ -5,18 +5,19 @@ package com.tinder.app.root.view import android.os.Bundle -import androidx.fragment.app.Fragment import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar +import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentStatePagerAdapter import androidx.viewpager.widget.ViewPager import com.google.android.material.tabs.TabLayout import com.tinder.R import com.tinder.app.socketio.chatroom.view.ChatRoomFragment +import com.tinder.app.socketio.chatroomservice.view.ChatRoomServiceFragment +import com.tinder.app.sse.stockprice.view.StockPriceFragment import com.tinder.app.websocket.echo.view.EchoBotFragment import com.tinder.app.websocket.gdax.view.GdaxFragment -import com.tinder.app.sse.stockprice.view.StockPriceFragment class DemoActivity : AppCompatActivity() { @@ -57,7 +58,8 @@ class DemoActivity : AppCompatActivity() { "WS - Echo Bot" to { EchoBotFragment() }, "WS - GDAX" to { GdaxFragment() }, "SSE - Stock Price" to { StockPriceFragment() }, - "SocketIo - Chat Room" to { ChatRoomFragment() } + "SocketIo - Chat Room" to { ChatRoomFragment() }, + "SocketIo - Chat Room Service" to { ChatRoomServiceFragment() } ) } } diff --git a/demo/src/main/java/com/tinder/app/socketio/chatroomservice/view/ChatRoomServiceFragment.kt b/demo/src/main/java/com/tinder/app/socketio/chatroomservice/view/ChatRoomServiceFragment.kt new file mode 100644 index 00000000..ffdc5942 --- /dev/null +++ b/demo/src/main/java/com/tinder/app/socketio/chatroomservice/view/ChatRoomServiceFragment.kt @@ -0,0 +1,72 @@ +/* + * © 2019 Match Group, LLC. + */ + +package com.tinder.app.socketio.chatroomservice.view + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.Bundle +import android.os.IBinder +import android.os.Messenger +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.tinder.R +import com.tinder.service.ChatRoomSocketIoService +import timber.log.Timber + +class ChatRoomServiceFragment : Fragment() { + + private var messenger: Messenger? = null + private var isBound = false + + private val serviceConnection = object : ServiceConnection { + + override fun onServiceConnected(name: ComponentName?, service: IBinder?) { + messenger = Messenger(service) + isBound = true + + Timber.d("chat onServiceConnected") + } + + override fun onServiceDisconnected(name: ComponentName?) { + messenger = null + isBound = false + + Timber.d("chat onServiceDisconnected") + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = inflater.inflate(R.layout.fragment_chatroom_service, container, false) as View + return view + } + + override fun onStart() { + super.onStart() + bindService() + } + + override fun onDestroy() { + super.onDestroy() + unbindService() + } + + private fun bindService() { + val intent = Intent(activity, ChatRoomSocketIoService::class.java) + activity?.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE) + } + + private fun unbindService() { + activity?.unbindService(serviceConnection) + isBound = false + } +} \ No newline at end of file diff --git a/demo/src/main/java/com/tinder/service/ChatRoomSocketIoService.kt b/demo/src/main/java/com/tinder/service/ChatRoomSocketIoService.kt new file mode 100644 index 00000000..ab7513ba --- /dev/null +++ b/demo/src/main/java/com/tinder/service/ChatRoomSocketIoService.kt @@ -0,0 +1,160 @@ +/* + * © 2018 Match Group, LLC. + */ + +package com.tinder.service + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Intent +import android.os.Handler +import android.os.IBinder +import android.os.Messenger +import androidx.lifecycle.LifecycleService +import com.tinder.R +import com.tinder.app.socketio.chatroom.api.AddUserTopic +import com.tinder.app.socketio.chatroom.api.ChatRoomService +import com.tinder.app.socketio.chatroom.api.NewMessageTopic +import com.tinder.app.socketio.chatroom.api.TypingStartedTopic +import com.tinder.app.socketio.chatroom.api.TypingStoppedTopic +import com.tinder.app.socketio.chatroom.api.UserJoinedTopic +import com.tinder.app.socketio.chatroom.api.UserLeftTopic +import com.tinder.scarlet.Scarlet +import com.tinder.scarlet.lifecycle.android.AndroidLifecycle +import com.tinder.scarlet.messageadapter.moshi.MoshiMessageAdapter +import com.tinder.scarlet.socketio.SocketIoEvent +import com.tinder.scarlet.socketio.client.SocketIoClient +import com.tinder.scarlet.socketio.client.SocketIoEventName +import com.tinder.scarlet.streamadapter.rxjava2.RxJava2StreamAdapterFactory +import io.reactivex.Flowable +import io.reactivex.schedulers.Schedulers +import timber.log.Timber + +class ChatRoomSocketIoService : LifecycleService() { + + var incomingMessageCount = 0 + private val messenger = Messenger(IncomingHandler()) + + override fun onCreate() { + super.onCreate() + + val config = Scarlet.Configuration( + lifecycle = AndroidLifecycle.ofLifecycleServiceForeground(application, this), + messageAdapterFactories = listOf(MoshiMessageAdapter.Factory()), + streamAdapterFactories = listOf(RxJava2StreamAdapterFactory()) + ) + val serverUrl = "https://socket-io-chat.now.sh/" + + val scarlet = Scarlet( + SocketIoClient({ serverUrl }), + config + ) + + val chatRoomService = scarlet.create() + val addUserTopic = scarlet + .child(SocketIoEventName("add user"), config) + .create() + val newMessageTopic = scarlet + .child(SocketIoEventName("new message"), config) + .create() + val typingStartedTopic = scarlet + .child(SocketIoEventName("typing"), config) + .create() + val typingStoppedTopic = scarlet + .child(SocketIoEventName("stop typing"), config) + .create() + val userJoinedTopic = scarlet + .child(SocketIoEventName("user joined"), config) + .create() + val userLeftTopic = scarlet + .child(SocketIoEventName("user left"), config) + .create() + + val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager + + val notificationChannel = + NotificationChannel(channelId, "Default", NotificationManager.IMPORTANCE_HIGH) + notificationManager.createNotificationChannel(notificationChannel) + + Timber.d("chatroom scarlet created") + + chatRoomService.observeStateTransition() + .subscribe { + Timber.d("chatroom service: $it") + + val notification = createNotification { + setContentText("State Transition ${it.toState}") + } + notificationManager.notify(notificationIdCurrentState, notification) + } + + val username = "scarlet service" + + addUserTopic.observeSocketIoEvent() + .filter { it is SocketIoEvent.OnConnectionOpened } + .observeOn(Schedulers.io()) + .subscribe({ + addUserTopic.sendAddUser(username) + + Timber.d("chatroom added user: $it") + val notification = createNotification { + setContentText("Joined chatroom") + } + notificationManager.notify(notificationIdCurrentState, notification) + }, { e -> + Timber.e(e) + }) + + Flowable.fromPublisher(AndroidLifecycle.ofLifecycleServiceForeground(application, this)) + .subscribe({ + Timber.d("chatroom lifecycle: $it") + + val notification = createNotification { + setContentText("Lifecycle State: $it") + } + notificationManager.notify(notificationIdCurrentState, notification) + }) + + Flowable.merge( + newMessageTopic.observeNewMessage().map { "${it.username}: ${it.message}" }, + typingStartedTopic.observeTypingStarted().map { "${it.username} started typing" }, + typingStoppedTopic.observeTypingStopped().map { "${it.username} stopped typing" } + ).subscribe { + Timber.d("chatroom new message: $it") + + val notification = createNotification { + setContentText("$it") + } + + val notificationId = notificationIdIncomingMessage + incomingMessageCount + incomingMessageCount += 1 + notificationManager.notify(notificationId, notification) + } + } + + override fun onBind(intent: Intent?): IBinder? { + super.onBind(intent) + Timber.d("chatroom onbind") + return messenger.binder + } + + private fun createNotification(builder: Notification.Builder.() -> Unit): Notification { + return Notification.Builder(applicationContext, channelId) + .setSmallIcon(R.drawable.ic_action_info) + .setWhen(System.currentTimeMillis()) + .setContentTitle("Chat Room") + .setAutoCancel(true) + .setOngoing(true) + .apply(builder) + .build() + } + + private inner class IncomingHandler : Handler() + + companion object { + const val notificationIdCurrentState = 10000 + const val notificationIdIncomingMessage = 10001 + const val channelId = "default" + } +} diff --git a/demo/src/main/res/layout/fragment_chatroom_service.xml b/demo/src/main/res/layout/fragment_chatroom_service.xml new file mode 100644 index 00000000..19cc8a1b --- /dev/null +++ b/demo/src/main/res/layout/fragment_chatroom_service.xml @@ -0,0 +1,23 @@ + + + + + + + + \ No newline at end of file diff --git a/dependencies.gradle b/dependencies.gradle index 09fe8d1d..ff72df7b 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -3,14 +3,14 @@ */ ext { - minSdkVersion = 21 + minSdkVersion = 28 minSdkVersionAndroidLifecycle = 19 targetSdkVersion = 28 compileSdkVersion = 28 buildToolsVersion = '28.0.3' appCompat = 'androidx.appcompat:appcompat:1.0.0' - lifecycleExtensions = 'androidx.lifecycle:lifecycle-extensions:2.0.0-rc01' + lifecycleExtensions = 'androidx.lifecycle:lifecycle-extensions:2.0.0' material = 'com.google.android.material:material:1.0.0' multiDex = 'androidx.multidex:multidex:2.0.0' constraintLayout = 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3' diff --git a/gradle.properties b/gradle.properties index 6ab48e20..04789687 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,4 +4,4 @@ android.enableJetifier=true android.useAndroidX=true -version=0.2.2-alpha4 +version=0.2.3-alpha1 diff --git a/scarlet-lifecycle-android/src/main/java/com/tinder/scarlet/lifecycle/android/AndroidLifecycle.kt b/scarlet-lifecycle-android/src/main/java/com/tinder/scarlet/lifecycle/android/AndroidLifecycle.kt index fbdf3383..5b0f62cb 100755 --- a/scarlet-lifecycle-android/src/main/java/com/tinder/scarlet/lifecycle/android/AndroidLifecycle.kt +++ b/scarlet-lifecycle-android/src/main/java/com/tinder/scarlet/lifecycle/android/AndroidLifecycle.kt @@ -40,4 +40,18 @@ object AndroidLifecycle { ) .combineWith(ConnectivityOnLifecycle(application)) } + + @JvmStatic + @JvmOverloads + fun ofLifecycleServiceForeground( + application: Application, + lifecycleOwner: LifecycleOwner, + throttleTimeoutMillis: Long = ACTIVITY_THROTTLE_TIMEOUT_MILLIS + ): Lifecycle { + return LifecycleOwnerStartedLifecycle( + lifecycleOwner, + LifecycleRegistry(throttleTimeoutMillis) + ) + .combineWith(ConnectivityOnLifecycle(application)) + } } diff --git a/scarlet-lifecycle-android/src/main/java/com/tinder/scarlet/lifecycle/android/LifecycleOwnerStartedLifecycle.kt b/scarlet-lifecycle-android/src/main/java/com/tinder/scarlet/lifecycle/android/LifecycleOwnerStartedLifecycle.kt new file mode 100644 index 00000000..08c0dfa0 --- /dev/null +++ b/scarlet-lifecycle-android/src/main/java/com/tinder/scarlet/lifecycle/android/LifecycleOwnerStartedLifecycle.kt @@ -0,0 +1,39 @@ +/* + * © 2019 Match Group, LLC. + */ + +package com.tinder.scarlet.lifecycle.android + +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.OnLifecycleEvent +import com.tinder.scarlet.Lifecycle +import com.tinder.scarlet.LifecycleState +import com.tinder.scarlet.lifecycle.LifecycleRegistry + +internal class LifecycleOwnerStartedLifecycle( + private val lifecycleOwner: LifecycleOwner, + private val lifecycleRegistry: LifecycleRegistry +) : Lifecycle by lifecycleRegistry { + + init { + lifecycleOwner.lifecycle.addObserver(ALifecycleObserver()) + } + + private inner class ALifecycleObserver : LifecycleObserver { + @OnLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_STOP) + fun onPause() { + lifecycleRegistry.onNext(LifecycleState.Stopped) + } + + @OnLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_START) + fun onResume() { + lifecycleRegistry.onNext(LifecycleState.Started) + } + + @OnLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_DESTROY) + fun onDestroy() { + lifecycleRegistry.onComplete() + } + } +} \ No newline at end of file