Skip to content

Commit

Permalink
Continue decoupling of search data provisioning
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
keyboardsurfer authored and florina-muntenescu committed Apr 16, 2019
1 parent 9f4feb1 commit 52a6068
Show file tree
Hide file tree
Showing 16 changed files with 261 additions and 41 deletions.
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

<application
android:allowBackup="true"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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.app.Notification
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
import io.plaidapp.core.interfaces.SearchDataSourceFactory
import io.plaidapp.ui.PlaidApplication

/**
* Enable registering #SearchDataSourceFactory.
*/
abstract class FactoryRegistrationService : Service() {

override fun onBind(intent: Intent?): IBinder? {
TODO("not implemented")
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// TODO create an actual notification
if (Build.VERSION.SDK_INT >= 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()
}
}
46 changes: 46 additions & 0 deletions app/src/main/java/io/plaidapp/registry/SearchBroadcastReceiver.kt
Original file line number Diff line number Diff line change
@@ -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<out FactoryRegistrationService>
) : 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"
}
}
2 changes: 2 additions & 0 deletions app/src/main/java/io/plaidapp/ui/HomeActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -135,6 +136,7 @@ protected void onCreate(Bundle savedInstanceState) {
bindResources();

inject(this);
Notifications.registerChannel(this);

adapter = new FeedAdapter(this, columns, pocketInstalled);

Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/io/plaidapp/core/dagger/CoreComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -40,4 +41,5 @@ interface CoreComponent {
fun provideGson(): Gson
fun provideGsonConverterFactory(): GsonConverterFactory
fun provideCallAdapterFactory(): CoroutineCallAdapterFactory
fun registry(): SearchDataSourceFactoriesRegistry
}
3 changes: 0 additions & 3 deletions core/src/main/java/io/plaidapp/core/dagger/CoreDataModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<List<SearchDataSourceFactory>>(getAlreadyAvailableDataSourceFactories())
MutableLiveData<List<SearchDataSourceFactory>>()

val dataSourceFactories: LiveData<List<SearchDataSourceFactory>>
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<SearchDataSourceFactory> {
return factoryClassNames.map {
val clazz = Class.forName(it)
val instance = clazz.newInstance() as SearchFactoryProvider
instance.getFactory()
}
}
}
45 changes: 45 additions & 0 deletions core/src/main/java/io/plaidapp/core/ui/Notifications.kt
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
9 changes: 9 additions & 0 deletions designernews/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@
<service
android:name="io.plaidapp.core.designernews.data.votes.UpvoteStoryService"
android:exported="false" />

<!-- Enable decoupled registration of search sources. -->
<service android:name=".DesignerNewsFactoryRegistrationService" android:exported="false"/>

<receiver android:name=".DesignerNewsSearchBroadcastReceiver">
<intent-filter>
<action android:name="io.plaidapp.register.SEARCH_FACTORY" />
</intent-filter>
</receiver>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -63,8 +62,3 @@ fun inject(activity: LoginActivity) {
.build()
.inject(activity)
}

fun DesignerNewsSearchFactoryProvider.inject() {

DaggerDesignerNewsSearchComponent.create()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Loading

0 comments on commit 52a6068

Please sign in to comment.