Android-CleanArchitecture icon indicating copy to clipboard operation
Android-CleanArchitecture copied to clipboard

What about things like AppSettings?

Open risayew opened this issue 9 years ago • 12 comments

Good Day! I have some difficulties with stuff like isAuthorized(), isFirstStart(), etc ... . In particular isAuthorized is a flag indicating, that the user has been logged in. Which solution will be better?

  1. Should we write an interactor and access AppSettingsRepository?
  2. Or simply consider AppSettings as a kind of "ui setting" which lives in presentation layer. Therefore we can define, for example, an AppSettings interface and inject in particular AppSettingsImpl (wrapper-model built using SharedPreferences), which can be accessed even in "ui thread"?

Thanks in advance!

risayew avatar Jun 21 '16 20:06 risayew

To follow the clean coding practice you should not have to worry about where the settings are stored and retrieved from. So in your domain layer you have your interface to your AppSettingsRepository then in your data layer you have your AppSettingsRepositoryImpl which contains the implementation. Now this could be a database or SharedPreferences but your presentation layer does not care about this.

In your presentation layer, using Dagger in a module, you provide your app settings like so. For this example I will assume you are using SharedPreferences and will need a Context which can be satisfied somewhere else in the graph.

@Provides
@Singleton
AppSettingsRepository provideAppSettingsRepository( Context context ) {
    return new AppSettingsRepositoryImpl( context );
}

StuStirling avatar Jun 22 '16 13:06 StuStirling

@StuStirling Thank you very much for answer! Would you suggest to use Interactors for AppSettingsRepository?

risayew avatar Jun 23 '16 08:06 risayew

I'm no expert when it comes to clean coding so I can't give you a definitive answer like others probably can. My gut feeling is in this case it may be overkill to write interactors for each UseCase. If you get access to the AppSettingsRepository interface then this acts as your interactor; you don't see the implementation but you are still, "interacting", with the data layer.

StuStirling avatar Jun 23 '16 09:06 StuStirling

But in this case, on my opinion, it does not make big sense to split AppSettings into layers. Simply put both AppSettings interface and AppSettingsImpl into presentation layer and consider them as "implementation detail" of the front-end.

risayew avatar Jun 23 '16 10:06 risayew

Again, I can't speak with 100% conviction and feel free to disagree with me because I'm learning it myself and by no means an expert.

I guess it depends on your implementation. If you are dead certain that the implementation will not change in the future then I guess you could put it all in the presentation layer however, this isn't really presentation layer logic. Yes, some of the settings may affect what/how you display your data but the actual AppSettings interface is about the storage and retrieval of data/preferences.

I would put the AppSettings interface in the domain layer and it's implementation in the data layer.

StuStirling avatar Jun 23 '16 15:06 StuStirling

Just a heads up, don't use the Repository pattern for this.

I see a lot of people just copy/pasting patterns from this example and assuming that "this is it, these things are the only patterns i'll ever use". Please keep reading up on architecture and understand that this repo that @android10 created is just an example of Clean Architecture and does not include every little thing you might need. It's just an example for a simple app he made.

A Repository pattern is about a collection of Objects. A PersonRepository is a "database" of a lot of People. An AppSettingsRepository would be a "database" of a lot of AppSettings Objects. And i think i can conclude that you don't want a lot of these AppSetting Objects; you just want one. So that's the wrong pattern there.

What you need is a Component; a concrete interface through which you can access all these Settings in your app. This component can be just some simple class in your Data Layer that actually just uses SharedPrefs to save and load all these little settings.

But in this case, on my opinion, it does not make big sense to split AppSettings into layers. Simply put both AppSettings interface and AppSettingsImpl into presentation layer and consider them as "implementation detail" of the front-end.

That would depend on a few things. If they are only used in the Presentation Layer, then it can exist there. If you access them anywhere else, you're breaking boundaries. I can also argue that the implementation on how they are saved (Sharedprefs, textfile, sqlite) is something that should reside in the Data Layer.

Trikke avatar Jun 27 '16 12:06 Trikke

I put it in the Data Layer, and defined an interface in the Domain Layer. https://github.com/enuoCM/DE-MVP-Clean/blob/master/app/src/main/java/com/xixicm/de/data/storage/pref/Preferences.java

enuoCM avatar Aug 26 '16 09:08 enuoCM

Hello @Trikke , I'm new in Android Clean Architecture, and I'm trying to apply it for my app.

I’m considering what if we have to pass Android’s argument(s) from PresenterImpl to Interactor, while Interactor must be POJO?

As you said,

This component can be just some simple class in your Data Layer that actually just uses SharedPrefs to save and load all these little settings.“

  • In PresenterImpl:
@Override
public void loadSettings(Context context){
    this.mSettingsInteractor.execute(new LoadSettingsSubscriber(), context);
}

with mSettingInteractor is a LoadSettingsInteractor object (see below).

  • In abstract Interactor:
protected abstract Observable buildInteractorObservable(R params);

public void execute(Subscriber interactorSubcriber, R params){
    this.subscription = this.buildInteractorObservable(params)
            .subscribeOn(Schedulers.from(threadExecutor))
            .observeOn(postExecutionThread.getScheduler())
            .subscribe(interactorSubcriber);
}
  • and LoadSettingsInteractor, which implement Interactor for loading SharedPreferences, I override buildInteractorObservable:
@Override
protected Observable buildInteractorObservable(Context context) {
    return mSettingsRepository.loadPrefs(context);
  • Then in SettingsRepository of Data layer, I can using SharedPreferences as normal:
private loadPrefs(Context context){
    mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
}

as above way, in LoadSettingsInteractor, I have to import and using Android Context, so this class will not be POJO. Could you please help me find out the solution? Best regards.

hdsplash avatar Sep 22 '16 04:09 hdsplash

@hdsplash why would you need to pass a context from your Interactor? There's no need for that.

You should inject your Application's Context into the Repository (which is probably an @singleton anyway).

@Inject
  public Repository(Context appContext ,....)

Trikke avatar Sep 26 '16 13:09 Trikke

@Trikke Thank you, I also discovered same idea from this project.

hdsplash avatar Sep 30 '16 07:09 hdsplash

@Trikke Thank you for you comment.

Just to be sure that I have understood you correctly, for app settings (which is in my case it will be 1 object {isFirstInstallation, isNotificationsEnabled, ...etc}) I should: 1- create an interface in the domain layer, 2- implement the interface in the data layer, 3- use the interface in the presentation layer (ie, no need to use specific use case here to get isFirstInstallation, right?), or in any other layer.

Are the previous steps right?

Note: Sorry for my bad English.

MSayyaf avatar Apr 02 '17 07:04 MSayyaf

@Trikke I'm interesting your recommendation that using Component for shared stuffs. But I'm still don't know how I can do this in practice. In my app, I want to share user object for most of views except authentication views. I have a master component : AppComponent and master module : AppModule. I would provide the user object in AppModule so all subcomponent can access this. But the AppComponent has been built before user use login feature. So the user object can be null or non-null, mean I have to check for null everywhere I need to use it.

I would ask for a way to inject or integrate a module into an component lazily ( or any way that help to solved my situation). Please help to give me some recommendations for this.

Thanks

IHNEL avatar Apr 20 '17 10:04 IHNEL