Как реализовать репозиторий интерфейса в другом модуле проекта в AndroidStudio с использованием Dagger2 и Kotlin

Я хочу иметь 2 разные реактивные реализации интерфейса, которые получают местоположение пользователя в другом модуле проекта в AndroidStudio.

Так что, если быть точным, это может быть с помощью gms или только с помощью родного Android LocationManager.

Вот мой интерфейс репозитория:

interface RxLocationRepository {

@SuppressLint("MissingPermission")
fun onLocationUpdate(): Observable<Location>

fun stopLocationUpdates()
}

Итак, на данный момент я реализую этот интерфейс здесь, в классе, который находится в том же модуле проекта в AndroidStudio:

class RxLocationRepositoryImpl(val reactiveLocationProvider: ReactiveLocationProvider,
                           val reactiveLocationRequest: LocationRequest,
                           val isUsingLocationNativeApi: Boolean,
                           val locationManager: LocationManager,
                           val geoEventsDistanceMeters: Int,
                           val geoEventsIntervalSeconds: Int
) : RxLocationRepository {


var locationToPopulate: Location = Location(LocationManager.GPS_PROVIDER)
lateinit var mLocationCallbackNativeApi: LocationListener
private val subject: BehaviorSubject<Location> = BehaviorSubject.createDefault(locationToPopulate)
var locationEmitter: Observable<Location> = subject.hide()

init {
    configureEmitter()
}

@SuppressLint("MissingPermission")
private fun configureEmitter(){
    if (!isUsingLocationNativeApi)
      locationEmitter = reactiveLocationProvider.getUpdatedLocation(reactiveLocationRequest)
    else{
      configureNativeLocationEmitter()
    }
}

@SuppressLint("MissingPermission")
private fun configureNativeLocationEmitter() {

    mLocationCallbackNativeApi = object : LocationListener {

        override fun onLocationChanged(location: Location) {
            subject.onNext(location)
        }

        override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {}

        override fun onProviderEnabled(provider: String) {}

        override fun onProviderDisabled(provider: String) {}

    }

    try {
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
                (geoEventsIntervalSeconds * 1000).toLong(),
                geoEventsDistanceMeters.toFloat(),
                mLocationCallbackNativeApi,
                Looper.getMainLooper())
    } catch (ignored: IllegalArgumentException) {
        ignored.printStackTrace()
    }

    try {
        locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
                (geoEventsIntervalSeconds * 1000).toLong(),
                geoEventsDistanceMeters.toFloat(),
                mLocationCallbackNativeApi,
                Looper.getMainLooper())
    } catch (ignored: IllegalArgumentException) {
        ignored.printStackTrace()
    }
}

@SuppressLint("MissingPermission")
override fun onLocationUpdate(): Observable<Location> {
    return locationEmitter
}

override fun stopLocationUpdates() {
    if(isUsingLocationNativeApi)
    locationManager.removeUpdates(mLocationCallbackNativeApi)
  }
}

Так что все в порядке, но теперь я хочу иметь реализацию gms в другом модуле проекта (поэтому мне не нужно иметь зависимости gms в gradle), а также нативную реализацию для другого модуля в Android Studio.

Окончательная структура проекта в AndroidStudio будет выглядеть следующим образом: location_core, location_gms, location_native.

Location_core не должен знать о location_gms и native, я думаю, в отличие от них - они будут иметь зависимость от location_core.

Итак, теперь у меня есть только модуль проекта location_core и класс LocationClient, который предоставит контекст:

class LocationClient @Inject constructor(val nexoLocationManager: NexoLocationManager){
companion object {

    private var locationClient: LocationClient? = null

    fun obtainLocationClient(context: Context): LocationClient{
        val result = locationClient ?:
                DaggerLocationComponent
                        .builder()
                        .context(context)
                        .build()
                        .locationClient()

        locationClient = result
        return result
    }
  }
}

Он использует некий Dagger Component, который обеспечивает ему реализацию всех различных объектов. Итак, я хочу переместить этот LocationClient или сделать его абстрактным в модуле проекта location_core и переместить его реализацию в каждый модуль проекта location_gms и location_native в AndroidStudio.

Таким образом, класс LocationClient в каждом из них будет предоставлять различную реализацию этого RxRepository. На данный момент в каждом модуле проекта location_gms и location_native у меня есть только класс реализации, который реализует репозиторий rxLocationRepository (который я перемещаю из реализации, написанной выше) по-своему.

Проблема в том, что я точно не знаю, как управлять всем этим с помощью Dagger. Теперь я использую Dagger вот так в моем location_core, вот LocationComponent:

@Singleton
@Component(modules = arrayOf(LocationModule::class))
interface LocationComponent{
fun locationClient(): LocationClient

@Component.Builder
interface Builder {
    @BindsInstance
    fun context(context: Context): Builder
    fun build(): LocationComponent
 }

}

и его модуль:

@Module
class LocationModule {

//some stuff

    @Provides
@Singleton
fun providesRxLocationRepository(
                                 reactiveLocationProvider: ReactiveLocationProvider,
                                 reactiveLocationRequest: LocationRequest,
                                 @Named("CONFIG_LOCATION_USE_NATIVE_API")isUsingLocationNativeApi: Boolean,
                                 locationManager: LocationManager,
                                 @Named("CONFIG_LOCATION_GEO_EVENTS_DISTANCE_METERS")geoEventsDistanceMeters: Int,
                                 @Named("CONFIG_LOCATION_GEO_EVENTS_INTERVAL_SECONDS")geoEventsIntervalSeconds: Int
): RxLocationRepository = RxLocationRepositoryImpl(
        reactiveLocationProvider,
        reactiveLocationRequest,
        isUsingLocationNativeApi,
        locationManager,
        geoEventsDistanceMeters,
        geoEventsIntervalSeconds)

//some other stuff
}

Итак, как же написать фактический LocationClient в каждом из модулей — location_gms и location_native? Как обеспечить реализацию rxLocationRepository, которая находится в каждом из модулей проекта location_gms и location_native с Dagger, чтобы я мог просто использовать интерфейс RxLocationRepository в моем модуле location_core и не беспокоиться о его реализации, потому что он будет в каждый модуль проекта?

Конечно, я должен отметить, что эти 3 модуля никогда не будут вместе, я думаю, это будет 2 модуля в каждом build_variant, я думаю. Поэтому мне нужно избавиться от зависимости от служб Google в модуле проекта location_core build.gradle.

введите здесь описание изображения

ОБНОВЛЕНИЕ

Как я могу на самом деле не иметь компонента Dagger в моем location_core, в котором я использую его в тестах, таких как: val component = DaggerLocationTestInstrumentalComponent.builder().context(InstrumentationRegistry.getContext()).build() val database = component.testDatabase() val locationManager = component.locationClient().nexoLocationManager


person K.Os    schedule 11.11.2017    source источник


Ответы (1)


Конечно, я должен отметить, что эти 3 модуля никогда не будут вместе, я думаю, это будет 2 модуля в каждом build_variant, я думаю.

Это главное, на что следует обратить внимание. Определение вашего компонента не должно находиться внутри location_core. Вместо этого он должен быть на стороне «потребителя». Под потребителем я подразумеваю четвертый модуль, который фактически использует местоположение. Назовем его consumer_module.

Как бы я подошел к этой задаче.

  1. И location_gms, и location_native должны содержать LocationModule с разной реализацией. Используйте одно и то же имя пакета для LocationModule в обоих модулях.

  2. consumer_module содержит LocationComponent. В зависимости от варианта сборки он будет преобразован в LocationModule из location_gms или location_native.

Другой подход был бы таким же для LocationModule, но с созданием в consumer_module двух LocationComponent. Каждый в другом варианте сборки. При таком подходе вам не нужно сохранять одно и то же имя пакета для LocationModule.

person MyDogTom    schedule 13.11.2017
comment
Но как я могу внедрить и получить реальную реализацию интерфейса RxRepository в моем LocationManager в location_core? Я передал его моему конструктору. Также см. мой обновленный вопрос. Собственно, это нужно делать только в этих 3-х модулях, а не в 4-м - person K.Os; 14.11.2017
comment
В этом случае используйте другой подход, который я описал. Создайте версии LocationComponent для разных вариантов (например, вариант сборки). Один вкус зависит от location_gms, другой от location_native - person MyDogTom; 14.11.2017
comment
Я думаю, лучшим решением будет ваше предложение использовать Consumer_module на самом деле, спасибо - person K.Os; 15.11.2017