From 52a60687679c18fadde7d283d2a0acfec38eb654 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Wed, 3 Apr 2019 12:16:16 +0100 Subject: [PATCH] Continue decoupling of search data provisioning * Introduce new FOREGROUND_SERVICE permission. * Use BroadcastReceiver to allow components to sign up. * Remove reflection from registry. To use the features introduced here, extend SearchBroadcastReceiver and FactoryRegistrationService. Then add them to your feature module's AndroidManifest. Also add the intent-filter io.plaidapp.register.SEARCH_FACTORY to the BroadcastReceiver. SearchActivity fires the required broadcast. --- app/src/main/AndroidManifest.xml | 1 + .../registry/FactoryRegistrationService.kt | 56 +++++++++++++++++++ .../registry/SearchBroadcastReceiver.kt | 46 +++++++++++++++ .../java/io/plaidapp/ui/HomeActivity.java | 2 + .../io/plaidapp/core/dagger/CoreComponent.kt | 2 + .../io/plaidapp/core/dagger/CoreDataModule.kt | 3 - .../SearchDataSourceFactoriesRegistry.kt | 19 +------ .../java/io/plaidapp/core/ui/Notifications.kt | 45 +++++++++++++++ designernews/src/main/AndroidManifest.xml | 9 +++ .../DesignerNewsFactoryRegistrationService.kt | 34 +++++++++++ .../DesignerNewsSearchBroadcastReceiver.kt | 22 ++++++++ .../dagger/DesignerNewsSearchComponent.kt | 20 ++++++- .../plaidapp/designernews/dagger/Injector.kt | 6 -- .../designernews/dagger/SearchDataModule.kt | 10 ++-- .../DesignerNewsSearchFactoryProvider.kt | 22 +++++--- .../io/plaidapp/search/ui/SearchActivity.kt | 5 ++ 16 files changed, 261 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/io/plaidapp/registry/FactoryRegistrationService.kt create mode 100644 app/src/main/java/io/plaidapp/registry/SearchBroadcastReceiver.kt create mode 100644 core/src/main/java/io/plaidapp/core/ui/Notifications.kt create mode 100644 designernews/src/main/java/io/plaidapp/designernews/DesignerNewsFactoryRegistrationService.kt create mode 100644 designernews/src/main/java/io/plaidapp/designernews/DesignerNewsSearchBroadcastReceiver.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 40d7f5104..7228ad0bf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,6 +22,7 @@ + = Build.VERSION_CODES.O) { + startForeground( + 1, + Notification.Builder(this, "Plaid").setContentTitle( + "Registering Search Service" + ).build() + ) + } + registerFactory() + return super.onStartCommand(intent, flags, startId) + } + + abstract fun getFactory(): SearchDataSourceFactory + + private fun registerFactory() { + PlaidApplication.coreComponent(this).registry().add(getFactory()) + stopSelf() + } +} diff --git a/app/src/main/java/io/plaidapp/registry/SearchBroadcastReceiver.kt b/app/src/main/java/io/plaidapp/registry/SearchBroadcastReceiver.kt new file mode 100644 index 000000000..5a908ce84 --- /dev/null +++ b/app/src/main/java/io/plaidapp/registry/SearchBroadcastReceiver.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2019 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.plaidapp.registry + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Build + +/** + * Extend this receiver to enable hooking into Plaid's #SearchDataSourceFactoriesRegistry. + */ +abstract class SearchBroadcastReceiver( + private val target: Class +) : BroadcastReceiver() { + + override fun onReceive(context: Context?, intent: Intent?) { + // TODO handle intent + if (context != null) { + val startRegistrationService = Intent(context, target) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(startRegistrationService) + } else { + context.startService(startRegistrationService) + } + } + } + + companion object { + const val ACTION = "io.plaidapp.register.SEARCH_FACTORY" + } +} diff --git a/app/src/main/java/io/plaidapp/ui/HomeActivity.java b/app/src/main/java/io/plaidapp/ui/HomeActivity.java index f54301f41..bfae3f57a 100644 --- a/app/src/main/java/io/plaidapp/ui/HomeActivity.java +++ b/app/src/main/java/io/plaidapp/ui/HomeActivity.java @@ -73,6 +73,7 @@ import io.plaidapp.core.feed.FeedProgressUiModel; import io.plaidapp.core.ui.ConnectivityChecker; import io.plaidapp.core.ui.HomeGridItemAnimator; +import io.plaidapp.core.ui.Notifications; import io.plaidapp.core.ui.PlaidItemsList; import io.plaidapp.core.ui.filter.FilterAdapter; import io.plaidapp.core.ui.filter.FilterAnimator; @@ -135,6 +136,7 @@ protected void onCreate(Bundle savedInstanceState) { bindResources(); inject(this); + Notifications.registerChannel(this); adapter = new FeedAdapter(this, columns, pocketInstalled); diff --git a/core/src/main/java/io/plaidapp/core/dagger/CoreComponent.kt b/core/src/main/java/io/plaidapp/core/dagger/CoreComponent.kt index 6ab47d611..a9231c0a4 100644 --- a/core/src/main/java/io/plaidapp/core/dagger/CoreComponent.kt +++ b/core/src/main/java/io/plaidapp/core/dagger/CoreComponent.kt @@ -19,6 +19,7 @@ package io.plaidapp.core.dagger import com.google.gson.Gson import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory import dagger.Component +import io.plaidapp.core.interfaces.SearchDataSourceFactoriesRegistry import okhttp3.OkHttpClient import retrofit2.converter.gson.GsonConverterFactory import javax.inject.Singleton @@ -40,4 +41,5 @@ interface CoreComponent { fun provideGson(): Gson fun provideGsonConverterFactory(): GsonConverterFactory fun provideCallAdapterFactory(): CoroutineCallAdapterFactory + fun registry(): SearchDataSourceFactoriesRegistry } diff --git a/core/src/main/java/io/plaidapp/core/dagger/CoreDataModule.kt b/core/src/main/java/io/plaidapp/core/dagger/CoreDataModule.kt index ba3ec2ee9..8414ac33f 100644 --- a/core/src/main/java/io/plaidapp/core/dagger/CoreDataModule.kt +++ b/core/src/main/java/io/plaidapp/core/dagger/CoreDataModule.kt @@ -24,7 +24,6 @@ import io.plaidapp.core.BuildConfig import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.converter.gson.GsonConverterFactory -import javax.inject.Singleton /** * Dagger module to provide core data functionality. @@ -49,11 +48,9 @@ class CoreDataModule { @Provides fun provideCallAdapterFactory(): CoroutineCallAdapterFactory = CoroutineCallAdapterFactory() - @Singleton @Provides fun provideGson(): Gson = Gson() - @Singleton @Provides fun provideGsonConverterFactory(gson: Gson): GsonConverterFactory = GsonConverterFactory.create(gson) diff --git a/core/src/main/java/io/plaidapp/core/interfaces/SearchDataSourceFactoriesRegistry.kt b/core/src/main/java/io/plaidapp/core/interfaces/SearchDataSourceFactoriesRegistry.kt index 2b5d9fa9c..f77c398f8 100644 --- a/core/src/main/java/io/plaidapp/core/interfaces/SearchDataSourceFactoriesRegistry.kt +++ b/core/src/main/java/io/plaidapp/core/interfaces/SearchDataSourceFactoriesRegistry.kt @@ -20,38 +20,25 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import javax.inject.Inject -private const val designerNewsSearchDataSourceFactoryProviderClassName = - "io.plaidapp.designernews.domain.search.DesignerNewsSearchFactoryProvider" - -// TODO add dribbble as well -private val factoryClassNames = - listOf(designerNewsSearchDataSourceFactoryProviderClassName) - class SearchDataSourceFactoriesRegistry @Inject constructor() { private val _dataSourceFactories = - MutableLiveData>(getAlreadyAvailableDataSourceFactories()) + MutableLiveData>() val dataSourceFactories: LiveData> get() = _dataSourceFactories fun add(dataSourceFactory: SearchDataSourceFactory) { val existingDataSources = _dataSourceFactories.value.orEmpty().toMutableList() + if (existingDataSources.contains(dataSourceFactory)) return existingDataSources.add(dataSourceFactory) _dataSourceFactories.postValue(existingDataSources) } fun remove(dataSourceFactory: SearchDataSourceFactory) { val existingDataSources = _dataSourceFactories.value.orEmpty().toMutableList() + if (existingDataSources.contains(dataSourceFactory)) return existingDataSources.remove(dataSourceFactory) _dataSourceFactories.postValue(existingDataSources) } - - private fun getAlreadyAvailableDataSourceFactories(): List { - return factoryClassNames.map { - val clazz = Class.forName(it) - val instance = clazz.newInstance() as SearchFactoryProvider - instance.getFactory() - } - } } diff --git a/core/src/main/java/io/plaidapp/core/ui/Notifications.kt b/core/src/main/java/io/plaidapp/core/ui/Notifications.kt new file mode 100644 index 000000000..41059c5cf --- /dev/null +++ b/core/src/main/java/io/plaidapp/core/ui/Notifications.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2019 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.plaidapp.core.ui + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import android.content.Context.NOTIFICATION_SERVICE +import android.os.Build + +class Notifications { + + companion object { + @JvmStatic + fun registerChannel(context: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // Create the NotificationChannel + val name = "Plaid" + val descriptionText = "Plaid default notification registerChannel" + val importance = NotificationManager.IMPORTANCE_DEFAULT + val mChannel = NotificationChannel(name, name, importance) + mChannel.description = descriptionText + // Register the registerChannel with the system; you can't change the importance + // or other notification behaviors after this + val notificationManager = context.getSystemService(NOTIFICATION_SERVICE) as + NotificationManager + notificationManager.createNotificationChannel(mChannel) + } + } + } +} diff --git a/designernews/src/main/AndroidManifest.xml b/designernews/src/main/AndroidManifest.xml index 1207ec3a6..3fdbaed9e 100644 --- a/designernews/src/main/AndroidManifest.xml +++ b/designernews/src/main/AndroidManifest.xml @@ -74,6 +74,15 @@ + + + + + + + + + diff --git a/designernews/src/main/java/io/plaidapp/designernews/DesignerNewsFactoryRegistrationService.kt b/designernews/src/main/java/io/plaidapp/designernews/DesignerNewsFactoryRegistrationService.kt new file mode 100644 index 000000000..ca61d6d60 --- /dev/null +++ b/designernews/src/main/java/io/plaidapp/designernews/DesignerNewsFactoryRegistrationService.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.plaidapp.designernews + +import io.plaidapp.core.dagger.SharedPreferencesModule +import io.plaidapp.core.designernews.data.login.LoginLocalDataSource +import io.plaidapp.core.interfaces.SearchDataSourceFactory +import io.plaidapp.designernews.dagger.DaggerDesignerNewsSearchComponent +import io.plaidapp.registry.FactoryRegistrationService + +class DesignerNewsFactoryRegistrationService : FactoryRegistrationService() { + + override fun getFactory(): SearchDataSourceFactory { + return DaggerDesignerNewsSearchComponent.builder() + .sharedPreferencesModule( + SharedPreferencesModule(this, LoginLocalDataSource.DESIGNER_NEWS_PREF) + ).build() + .factory() + } +} diff --git a/designernews/src/main/java/io/plaidapp/designernews/DesignerNewsSearchBroadcastReceiver.kt b/designernews/src/main/java/io/plaidapp/designernews/DesignerNewsSearchBroadcastReceiver.kt new file mode 100644 index 000000000..bb45680bf --- /dev/null +++ b/designernews/src/main/java/io/plaidapp/designernews/DesignerNewsSearchBroadcastReceiver.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2019 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.plaidapp.designernews + +import io.plaidapp.registry.SearchBroadcastReceiver + +class DesignerNewsSearchBroadcastReceiver : + SearchBroadcastReceiver(DesignerNewsFactoryRegistrationService::class.java) diff --git a/designernews/src/main/java/io/plaidapp/designernews/dagger/DesignerNewsSearchComponent.kt b/designernews/src/main/java/io/plaidapp/designernews/dagger/DesignerNewsSearchComponent.kt index 05931629c..ff6ec2cdd 100644 --- a/designernews/src/main/java/io/plaidapp/designernews/dagger/DesignerNewsSearchComponent.kt +++ b/designernews/src/main/java/io/plaidapp/designernews/dagger/DesignerNewsSearchComponent.kt @@ -17,8 +17,24 @@ package io.plaidapp.designernews.dagger import dagger.Component +import io.plaidapp.core.dagger.CoreDataModule +import io.plaidapp.core.dagger.SharedPreferencesModule +import io.plaidapp.core.dagger.designernews.DesignerNewsDataModule +import io.plaidapp.core.dagger.scope.FeatureScope +import io.plaidapp.designernews.domain.search.DesignerNewsSearchDataSourceFactory @Component( - modules = [SearchDataModule::class] + modules = [ + CoreDataModule::class, + DataModule::class, + DesignerNewsDataModule::class, + SearchDataModule::class, + SharedPreferencesModule::class + ] ) -interface DesignerNewsSearchComponent +@FeatureScope +interface DesignerNewsSearchComponent { + + @FeatureScope + fun factory(): DesignerNewsSearchDataSourceFactory +} diff --git a/designernews/src/main/java/io/plaidapp/designernews/dagger/Injector.kt b/designernews/src/main/java/io/plaidapp/designernews/dagger/Injector.kt index e71ab78ab..15517c842 100644 --- a/designernews/src/main/java/io/plaidapp/designernews/dagger/Injector.kt +++ b/designernews/src/main/java/io/plaidapp/designernews/dagger/Injector.kt @@ -22,7 +22,6 @@ import `in`.uncod.android.bypass.Bypass import android.util.TypedValue import androidx.core.content.ContextCompat import io.plaidapp.core.dagger.MarkdownModule -import io.plaidapp.designernews.domain.search.DesignerNewsSearchFactoryProvider import io.plaidapp.designernews.ui.login.LoginActivity import io.plaidapp.designernews.ui.story.StoryActivity import io.plaidapp.ui.coreComponent @@ -63,8 +62,3 @@ fun inject(activity: LoginActivity) { .build() .inject(activity) } - -fun DesignerNewsSearchFactoryProvider.inject() { - - DaggerDesignerNewsSearchComponent.create() -} diff --git a/designernews/src/main/java/io/plaidapp/designernews/dagger/SearchDataModule.kt b/designernews/src/main/java/io/plaidapp/designernews/dagger/SearchDataModule.kt index c893c1782..7dfd2e66e 100644 --- a/designernews/src/main/java/io/plaidapp/designernews/dagger/SearchDataModule.kt +++ b/designernews/src/main/java/io/plaidapp/designernews/dagger/SearchDataModule.kt @@ -18,20 +18,18 @@ package io.plaidapp.designernews.dagger import dagger.Module import dagger.Provides +import io.plaidapp.core.dagger.scope.FeatureScope import io.plaidapp.core.designernews.data.stories.StoriesRepository -import io.plaidapp.core.interfaces.SearchDataSourceFactoriesRegistry import io.plaidapp.designernews.domain.search.DesignerNewsSearchDataSourceFactory @Module class SearchDataModule { @Provides + @FeatureScope fun designerNewsSearchDataSourceFactory( - repository: StoriesRepository, - registry: SearchDataSourceFactoriesRegistry + repository: StoriesRepository ): DesignerNewsSearchDataSourceFactory { - val factory = DesignerNewsSearchDataSourceFactory(repository) - registry.add(factory) - return factory + return DesignerNewsSearchDataSourceFactory(repository) } } diff --git a/designernews/src/main/java/io/plaidapp/designernews/domain/search/DesignerNewsSearchFactoryProvider.kt b/designernews/src/main/java/io/plaidapp/designernews/domain/search/DesignerNewsSearchFactoryProvider.kt index 27711f515..0778aca07 100644 --- a/designernews/src/main/java/io/plaidapp/designernews/domain/search/DesignerNewsSearchFactoryProvider.kt +++ b/designernews/src/main/java/io/plaidapp/designernews/domain/search/DesignerNewsSearchFactoryProvider.kt @@ -16,22 +16,28 @@ package io.plaidapp.designernews.domain.search -import io.plaidapp.core.designernews.data.stories.StoriesRepository +import android.content.Context +import io.plaidapp.core.dagger.SharedPreferencesModule +import io.plaidapp.core.designernews.data.login.LoginLocalDataSource import io.plaidapp.core.interfaces.SearchDataSourceFactory import io.plaidapp.core.interfaces.SearchFactoryProvider -import io.plaidapp.designernews.dagger.inject +import io.plaidapp.designernews.dagger.DaggerDesignerNewsSearchComponent import javax.inject.Inject -class DesignerNewsSearchFactoryProvider : SearchFactoryProvider { +class DesignerNewsSearchFactoryProvider(context: Context) : SearchFactoryProvider { + + @Inject + lateinit var _factory: DesignerNewsSearchDataSourceFactory init { - inject() + DaggerDesignerNewsSearchComponent.builder() + .sharedPreferencesModule( + SharedPreferencesModule(context, LoginLocalDataSource.DESIGNER_NEWS_PREF) + ) + .build() } - @Inject - lateinit var repository: StoriesRepository - override fun getFactory(): SearchDataSourceFactory { - return DesignerNewsSearchDataSourceFactory(repository) + return _factory } } diff --git a/search/src/main/java/io/plaidapp/search/ui/SearchActivity.kt b/search/src/main/java/io/plaidapp/search/ui/SearchActivity.kt index a4bd1856b..c1680aede 100644 --- a/search/src/main/java/io/plaidapp/search/ui/SearchActivity.kt +++ b/search/src/main/java/io/plaidapp/search/ui/SearchActivity.kt @@ -18,6 +18,7 @@ package io.plaidapp.search.ui import android.app.SearchManager import android.app.SharedElementCallback +import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.graphics.Point @@ -90,6 +91,7 @@ class SearchActivity : AppCompatActivity() { private var focusQuery = true private lateinit var feedAdapter: FeedAdapter + private lateinit var designerNewsSearchBroadcastReceiver: BroadcastReceiver @Inject lateinit var viewModel: SearchViewModel @@ -108,6 +110,8 @@ class SearchActivity : AppCompatActivity() { feedAdapter = FeedAdapter(this, columns, pocketInstalled) + sendBroadcast(Intent("io.plaidapp.register.SEARCH_FACTORY")) + viewModel.searchResults.observe(this, Observer { searchUiModel -> if (searchUiModel.items.isNotEmpty()) { if (results.visibility != View.VISIBLE) { @@ -159,6 +163,7 @@ class SearchActivity : AppCompatActivity() { override fun onLoadMore() { viewModel.loadMore() } + override fun isDataLoading(): Boolean { return viewModel.searchProgress.value?.isLoading ?: false }