mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 13:57:10 +09:00
Analytics module setup (#618)
This commit is contained in:
parent
f8c703b158
commit
1beb0312a0
26 changed files with 241 additions and 12 deletions
|
@ -15,6 +15,7 @@
|
|||
|
||||
### Fixes & tech 🚒
|
||||
|
||||
* Setup analytics module (#618)
|
||||
* Turn-into in multi-select mode should not break selected/unselected-state-related logic (#621)
|
||||
* App should not crash when user presses change-style button or open-action-menu button on block-toolbar when document's title is focused (#620)
|
||||
* Drag-and-drop area issues on home dashboard (#570)
|
||||
|
|
1
analytics/.gitignore
vendored
Normal file
1
analytics/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/build
|
31
analytics/build.gradle
Normal file
31
analytics/build.gradle
Normal file
|
@ -0,0 +1,31 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
def config = rootProject.extensions.getByName("ext")
|
||||
|
||||
compileSdkVersion config["compile_sdk"]
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion config["min_sdk"]
|
||||
targetSdkVersion config["target_sdk"]
|
||||
testInstrumentationRunner config["test_runner"]
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def app = rootProject.ext.mainApplication
|
||||
def analytics = rootProject.ext.analytics
|
||||
implementation app.kotlin
|
||||
implementation app.coroutines
|
||||
implementation app.timber
|
||||
implementation analytics.amplitude
|
||||
}
|
0
analytics/consumer-rules.pro
Normal file
0
analytics/consumer-rules.pro
Normal file
21
analytics/proguard-rules.pro
vendored
Normal file
21
analytics/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
1
analytics/src/main/AndroidManifest.xml
Normal file
1
analytics/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1 @@
|
|||
<manifest package="com.agileburo.anytype.analytics" />
|
|
@ -0,0 +1,22 @@
|
|||
package com.agileburo.anytype.analytics.base
|
||||
|
||||
import com.agileburo.anytype.analytics.event.Event
|
||||
import com.agileburo.anytype.analytics.props.UserProperty
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface Analytics {
|
||||
/**
|
||||
* Deliver a new [Event] to an analytics tracker.
|
||||
*/
|
||||
suspend fun registerEvent(event: Event)
|
||||
|
||||
/**
|
||||
* Update current [UserProperty]
|
||||
*/
|
||||
fun updateUserProperty(property: UserProperty)
|
||||
|
||||
/**
|
||||
* Return a stream of [Event]
|
||||
*/
|
||||
fun observeEvents(): Flow<Event>
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.agileburo.anytype.analytics.base
|
||||
|
||||
import com.agileburo.anytype.analytics.event.Event
|
||||
import com.agileburo.anytype.analytics.props.UserProperty
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.consumeAsFlow
|
||||
|
||||
class DefaultAnalytics : Analytics {
|
||||
private val events = Channel<Event>()
|
||||
override suspend fun registerEvent(event: Event) = events.send(event)
|
||||
override fun observeEvents(): Flow<Event> = events.consumeAsFlow()
|
||||
override fun updateUserProperty(property: UserProperty) = TODO("Not yet implemented")
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.agileburo.anytype.analytics.event
|
||||
|
||||
import com.agileburo.anytype.analytics.props.Props
|
||||
|
||||
interface Event {
|
||||
|
||||
val name: String
|
||||
val prettified: String
|
||||
val props: Props
|
||||
|
||||
data class Duration(
|
||||
val total: Long?,
|
||||
val middleware: Long?
|
||||
)
|
||||
|
||||
data class Anytype(
|
||||
override val name: String,
|
||||
override val prettified: String,
|
||||
override val props: Props,
|
||||
val duration: Duration?
|
||||
) : Event
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.agileburo.anytype.analytics.props
|
||||
|
||||
data class Props(val map: Map<String?, Any?>) {
|
||||
private val default = map.withDefault { null }
|
||||
|
||||
companion object {
|
||||
fun empty() = Props(emptyMap())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package com.agileburo.anytype.analytics.props
|
||||
|
||||
interface UserProperty
|
|
@ -0,0 +1,26 @@
|
|||
package com.agileburo.anytype.analytics.tracker
|
||||
|
||||
import com.agileburo.anytype.analytics.base.Analytics
|
||||
import com.amplitude.api.Amplitude
|
||||
import com.amplitude.api.AmplitudeClient
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AmplitudeTracker(
|
||||
private val scope: CoroutineScope,
|
||||
private val analytics: Analytics
|
||||
) : Tracker {
|
||||
|
||||
private val tracker: AmplitudeClient get() = Amplitude.getInstance()
|
||||
|
||||
init {
|
||||
scope.launch { startRegisteringEvents() }
|
||||
}
|
||||
|
||||
private suspend fun startRegisteringEvents() {
|
||||
analytics.observeEvents().collect { event ->
|
||||
tracker.logEvent(event.name)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package com.agileburo.anytype.analytics.tracker
|
||||
|
||||
interface Tracker
|
|
@ -85,12 +85,14 @@ dependencies {
|
|||
implementation project(':library-kanban-widget')
|
||||
implementation project(':library-page-icon-picker-widget')
|
||||
implementation project(':library-emojifier')
|
||||
implementation project(':analytics')
|
||||
|
||||
def applicationDependencies = rootProject.ext.mainApplication
|
||||
def unitTestDependencies = rootProject.ext.unitTesting
|
||||
def acceptanceTesting = rootProject.ext.acceptanceTesting
|
||||
def devDependencies = rootProject.ext.development
|
||||
def databaseDependencies = rootProject.ext.db
|
||||
def analyticsDependencies = rootProject.ext.analytics
|
||||
|
||||
|
||||
//Compile time dependencies
|
||||
|
@ -131,6 +133,8 @@ dependencies {
|
|||
implementation devDependencies.stetho
|
||||
implementation applicationDependencies.exoPlayer
|
||||
|
||||
implementation analyticsDependencies.amplitude
|
||||
|
||||
//Unit/Integration tests dependencies
|
||||
testImplementation unitTestDependencies.junit
|
||||
testImplementation unitTestDependencies.robolectric
|
||||
|
|
|
@ -16,6 +16,7 @@ import com.agileburo.anytype.domain.download.DownloadFile
|
|||
import com.agileburo.anytype.domain.event.interactor.InterceptEvents
|
||||
import com.agileburo.anytype.domain.event.model.Event
|
||||
import com.agileburo.anytype.domain.event.model.Payload
|
||||
import com.agileburo.anytype.domain.icon.DocumentEmojiIconProvider
|
||||
import com.agileburo.anytype.domain.misc.UrlBuilder
|
||||
import com.agileburo.anytype.domain.page.*
|
||||
import com.agileburo.anytype.domain.page.bookmark.SetupBookmark
|
||||
|
@ -88,9 +89,13 @@ open class EditorTestSetup {
|
|||
lateinit var uriMatcher: Clipboard.UriMatcher
|
||||
@Mock
|
||||
lateinit var repo: BlockRepository
|
||||
|
||||
@Mock
|
||||
lateinit var clipboard: Clipboard
|
||||
|
||||
@Mock
|
||||
lateinit var documentEmojiIconProvider: DocumentEmojiIconProvider
|
||||
|
||||
val root: String = "rootId123"
|
||||
|
||||
private val config = Config(
|
||||
|
@ -115,9 +120,9 @@ open class EditorTestSetup {
|
|||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
splitBlock = SplitBlock(repo)
|
||||
createPage = CreatePage(repo)
|
||||
createPage = CreatePage(repo, documentEmojiIconProvider)
|
||||
archiveDocument = ArchiveDocument(repo)
|
||||
createDocument = CreateDocument(repo)
|
||||
createDocument = CreateDocument(repo, documentEmojiIconProvider)
|
||||
undo = Undo(repo)
|
||||
redo = Redo(repo)
|
||||
replaceBlock = ReplaceBlock(repo)
|
||||
|
|
|
@ -6,14 +6,20 @@ import androidx.emoji.text.EmojiCompat
|
|||
import androidx.emoji.text.FontRequestEmojiCompatConfig
|
||||
import com.agileburo.anytype.BuildConfig
|
||||
import com.agileburo.anytype.R
|
||||
import com.agileburo.anytype.analytics.tracker.AmplitudeTracker
|
||||
import com.agileburo.anytype.core_utils.tools.CrashlyticsTree
|
||||
import com.agileburo.anytype.di.common.ComponentManager
|
||||
import com.agileburo.anytype.di.main.*
|
||||
import com.amplitude.api.Amplitude
|
||||
import com.facebook.stetho.Stetho
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class AndroidApplication : Application() {
|
||||
|
||||
@Inject
|
||||
lateinit var amplitudeTracker: AmplitudeTracker
|
||||
|
||||
private val main: MainComponent by lazy {
|
||||
DaggerMainComponent
|
||||
.builder()
|
||||
|
@ -22,6 +28,7 @@ class AndroidApplication : Application() {
|
|||
.configModule(ConfigModule())
|
||||
.utilModule(UtilModule())
|
||||
.deviceModule(DeviceModule())
|
||||
.analyticsModule(AnalyticsModule())
|
||||
.build()
|
||||
}
|
||||
|
||||
|
@ -31,7 +38,8 @@ class AndroidApplication : Application() {
|
|||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
main.inject(this)
|
||||
setupAnalytics()
|
||||
setupEmojiCompat()
|
||||
setupTimber()
|
||||
setupStetho()
|
||||
|
@ -59,4 +67,8 @@ class AndroidApplication : Application() {
|
|||
if (BuildConfig.DEBUG)
|
||||
Stetho.initializeWithDefaults(this)
|
||||
}
|
||||
|
||||
private fun setupAnalytics() {
|
||||
Amplitude.getInstance().initialize(this, getString(R.string.amplitude_api_key))
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package com.agileburo.anytype.di.feature
|
||||
|
||||
import com.agileburo.anytype.analytics.base.Analytics
|
||||
import com.agileburo.anytype.core_utils.di.scope.PerScreen
|
||||
import com.agileburo.anytype.domain.auth.interactor.CheckAuthorizationStatus
|
||||
import com.agileburo.anytype.domain.auth.interactor.LaunchAccount
|
||||
|
@ -39,14 +40,15 @@ class SplashModule {
|
|||
fun provideSplashViewModelFactory(
|
||||
checkAuthorizationStatus: CheckAuthorizationStatus,
|
||||
launchAccount: LaunchAccount,
|
||||
launchWallet: LaunchWallet
|
||||
launchWallet: LaunchWallet,
|
||||
analytics: Analytics
|
||||
): SplashViewModelFactory = SplashViewModelFactory(
|
||||
checkAuthorizationStatus = checkAuthorizationStatus,
|
||||
launchAccount = launchAccount,
|
||||
launchWallet = launchWallet
|
||||
launchWallet = launchWallet,
|
||||
analytics = analytics
|
||||
)
|
||||
|
||||
|
||||
@PerScreen
|
||||
@Provides
|
||||
fun provideCheckAuthorizationStatusUseCase(
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package com.agileburo.anytype.di.main
|
||||
|
||||
import com.agileburo.anytype.analytics.base.Analytics
|
||||
import com.agileburo.anytype.analytics.base.DefaultAnalytics
|
||||
import com.agileburo.anytype.analytics.tracker.AmplitudeTracker
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
class AnalyticsModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAnalytics(): Analytics = DefaultAnalytics()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAmplitudeTracker(
|
||||
analytics: Analytics
|
||||
): AmplitudeTracker = AmplitudeTracker(
|
||||
scope = GlobalScope,
|
||||
analytics = analytics
|
||||
)
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package com.agileburo.anytype.di.main
|
||||
|
||||
import com.agileburo.anytype.app.AndroidApplication
|
||||
import com.agileburo.anytype.di.feature.*
|
||||
import dagger.Component
|
||||
import javax.inject.Singleton
|
||||
|
@ -14,10 +15,13 @@ import javax.inject.Singleton
|
|||
DeviceModule::class,
|
||||
UtilModule::class,
|
||||
EmojiModule::class,
|
||||
ClipboardModule::class
|
||||
ClipboardModule::class,
|
||||
AnalyticsModule::class
|
||||
]
|
||||
)
|
||||
interface MainComponent {
|
||||
fun inject(app: AndroidApplication)
|
||||
|
||||
fun authComponentBuilder(): AuthSubComponent.Builder
|
||||
fun profileComponentBuilder(): ProfileSubComponent.Builder
|
||||
fun splashComponentBuilder(): SplashSubComponent.Builder
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<resources>
|
||||
|
||||
<string name="app_name">Anytype</string>
|
||||
<string name="amplitude_api_key">d4a910f56d6b05f2b9bd47041bd2fcda</string>
|
||||
|
||||
<string name="example_1">
|
||||
To layout text on Android, the system does a lot of work. Each glyph is resolved — dependent on font, locale, size, font features (like bold or italic). Then the system will resolve rules for how they line up, combine, or merge as they form words. After all that, the words can finally be wrapped into available space.
|
||||
|
|
|
@ -78,6 +78,10 @@ ext {
|
|||
|
||||
room_version = '2.2.0'
|
||||
|
||||
// Analytics
|
||||
|
||||
amplitude_version = '2.23.2'
|
||||
|
||||
mainApplication = [
|
||||
kotlin: "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version",
|
||||
coroutines: "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version",
|
||||
|
@ -175,4 +179,8 @@ ext {
|
|||
libraryPageIconPicker = [
|
||||
emojiJava: "com.vdurmont:emoji-java:5.1.1"
|
||||
]
|
||||
|
||||
analytics = [
|
||||
amplitude: "com.amplitude:android-sdk:$amplitude_version"
|
||||
]
|
||||
}
|
|
@ -28,6 +28,7 @@ dependencies {
|
|||
implementation project(':core-ui')
|
||||
implementation project(':library-page-icon-picker-widget')
|
||||
implementation project(':library-emojifier')
|
||||
implementation project(':analytics')
|
||||
|
||||
def applicationDependencies = rootProject.ext.mainApplication
|
||||
def unitTestDependencies = rootProject.ext.unitTesting
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.agileburo.anytype.presentation.splash
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.agileburo.anytype.analytics.base.Analytics
|
||||
import com.agileburo.anytype.core_utils.common.EventWrapper
|
||||
import com.agileburo.anytype.core_utils.ui.ViewState
|
||||
import com.agileburo.anytype.domain.auth.interactor.CheckAuthorizationStatus
|
||||
|
@ -20,6 +21,7 @@ import timber.log.Timber
|
|||
* on 2019-10-21.
|
||||
*/
|
||||
class SplashViewModel(
|
||||
private val analytics: Analytics,
|
||||
private val checkAuthorizationStatus: CheckAuthorizationStatus,
|
||||
private val launchWallet: LaunchWallet,
|
||||
private val launchAccount: LaunchAccount
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.agileburo.anytype.presentation.splash
|
|||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.agileburo.anytype.analytics.base.Analytics
|
||||
import com.agileburo.anytype.domain.auth.interactor.CheckAuthorizationStatus
|
||||
import com.agileburo.anytype.domain.auth.interactor.LaunchAccount
|
||||
import com.agileburo.anytype.domain.auth.interactor.LaunchWallet
|
||||
|
@ -14,7 +15,8 @@ import com.agileburo.anytype.domain.auth.interactor.LaunchWallet
|
|||
class SplashViewModelFactory(
|
||||
private val checkAuthorizationStatus: CheckAuthorizationStatus,
|
||||
private val launchAccount: LaunchAccount,
|
||||
private val launchWallet: LaunchWallet
|
||||
private val launchWallet: LaunchWallet,
|
||||
private val analytics: Analytics
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
@ -22,6 +24,7 @@ class SplashViewModelFactory(
|
|||
SplashViewModel(
|
||||
checkAuthorizationStatus = checkAuthorizationStatus,
|
||||
launchAccount = launchAccount,
|
||||
launchWallet = launchWallet
|
||||
launchWallet = launchWallet,
|
||||
analytics = analytics
|
||||
) as T
|
||||
}
|
|
@ -2,6 +2,7 @@ package com.agileburo.anytype.presentation.splash
|
|||
|
||||
import MockDataFactory
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.agileburo.anytype.analytics.base.Analytics
|
||||
import com.agileburo.anytype.core_utils.ui.ViewState
|
||||
import com.agileburo.anytype.domain.auth.interactor.CheckAuthorizationStatus
|
||||
import com.agileburo.anytype.domain.auth.interactor.LaunchAccount
|
||||
|
@ -36,6 +37,9 @@ class SplashViewModelTest {
|
|||
@Mock
|
||||
lateinit var launchWallet: LaunchWallet
|
||||
|
||||
@Mock
|
||||
lateinit var analytics: Analytics
|
||||
|
||||
lateinit var vm: SplashViewModel
|
||||
|
||||
|
||||
|
@ -45,7 +49,8 @@ class SplashViewModelTest {
|
|||
vm = SplashViewModel(
|
||||
checkAuthorizationStatus = checkAuthorizationStatus,
|
||||
launchAccount = launchAccount,
|
||||
launchWallet = launchWallet
|
||||
launchWallet = launchWallet,
|
||||
analytics = analytics
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -12,5 +12,6 @@ include ':app',
|
|||
':library-kanban-widget',
|
||||
':library-page-icon-picker-widget',
|
||||
':library-emojifier',
|
||||
':sample'
|
||||
include ':clipboard'
|
||||
':sample',
|
||||
':clipboard',
|
||||
':analytics'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue