1
0
Fork 0
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:
Evgenii Kozlov 2020-08-01 17:03:01 +03:00 committed by GitHub
parent f8c703b158
commit 1beb0312a0
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 241 additions and 12 deletions

View file

@ -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
View file

@ -0,0 +1 @@
/build

31
analytics/build.gradle Normal file
View 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
}

View file

21
analytics/proguard-rules.pro vendored Normal file
View 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

View file

@ -0,0 +1 @@
<manifest package="com.agileburo.anytype.analytics" />

View file

@ -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>
}

View file

@ -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")
}

View file

@ -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
}

View file

@ -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())
}
}

View file

@ -0,0 +1,3 @@
package com.agileburo.anytype.analytics.props
interface UserProperty

View file

@ -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)
}
}
}

View file

@ -0,0 +1,3 @@
package com.agileburo.anytype.analytics.tracker
interface Tracker

View file

@ -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

View file

@ -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)

View file

@ -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))
}
}

View file

@ -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(

View file

@ -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
)
}

View file

@ -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

View file

@ -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 resolveddependent 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.

View file

@ -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"
]
}

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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
)
}

View file

@ -12,5 +12,6 @@ include ':app',
':library-kanban-widget',
':library-page-icon-picker-widget',
':library-emojifier',
':sample'
include ':clipboard'
':sample',
':clipboard',
':analytics'