Code Monkey home page Code Monkey logo

wolmo-networking-android's Introduction

Build Status Release Coverage Status

WOLMO NETWORKING (ANDROID)

The NETWORKING module provides a separation from the network tier from the rest of the project while also providing offline support capabilities.

Features

  • Fully compatible with Dagger and WOLMO Core v2.
  • Perform REST API calls using a preconfigured Retrofit client
  • HTTP requests logging in LogCat preconfigured
  • Time classes handling preconfigured with Retrofit and custom serializers/deserializers
  • Cache (offline) mechanisms base classes
  • Repository pattern oriented architecture

Installation

Import the module as a library in your project using Gradle:

root build.gradle

allprojects {
  repositories {
    ...
    maven { url 'https://jitpack.io' }
  }
}

your app module build.gradle

dependencies {
        compile 'com.github.Wolox:wolmo-networking-android:master-SNAPSHOT'
}

Note: The above line will download the latest version of the module, if you want to run a specific version replace master-SNAPSHOT with 2.0.0 or any other version. Available versions can be found here: Github releases

How to use

Applications that use wolmo-network don't need to extend NetworkingApplication anymore. WOLMO Networking provides the modules to build and configure the dependencies of Retrofit, Gson and OkHttp, it also provides a default NetworkingComponent to simplify the creation and reduce configuration on common scenarios. For it to work you only need to provide it with the baseUrl, an optional list of interceptors to add to okHttp and an optional list of Gson type adapters.

This version of Networking adds the following Modules:

  • CachingModule: Provides all the dependencies to cache request.
  • GsonModule: Provides a Gson instance and all the dependencies to build it.
  • OkHttpClientModule: Provides a instance of OkHttpClient and all the dependencies to build it.
  • NetworkingModule: Provides a instance of Retrofit.

There are two possible ways to use it:

Add a component dependency to NetworkingComponent

You can use it this way when you don't need further customizations.

@ApplicationScope
@Component(dependencies = {NetworkingComponent.class},
           modules = { AndroidSupportInjectionModule.class, DefaultModule.class,
                   ContextModule.class, AppModule.class })
public interface AppComponent extends AndroidInjector<BootstrapApplication> {

    @Component.Builder
    abstract class Builder extends AndroidInjector.Builder<BootstrapApplication> {

        @BindsInstance
        public abstract Builder application(Application application);

        public abstract Builder networkingComponent(NetworkingComponent networkingComponent);

    }
}

In your application you need to build the NetworkingComponent like:

public class BootstrapApplication extends WolmoApplication {

    @Override
    public void onInit() { ... }

    @Override
    protected AndroidInjector<BootstrapApplication> applicationInjector() {
        return DaggerAppComponent.builder().networkingComponent(buildDaggerNetworkingComponent())
            .application(this)
            .create(this);
    }

    private NetworkingComponent buildDaggerNetworkingComponent() {
        NetworkingComponent.Builder builder =
            DaggerNetworkingComponent.builder().baseUrl(Configuration.EXAMPLE_CONFIGURATION_KEY)
                .gsonNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);

        if (BuildConfig.DEBUG) {
            builder.okHttpInterceptors(
                buildHttpLoggingInterceptor(Level.BODY), new ChuckInterceptor(this));
        }

        return builder.build();
    }

    /**
     * Returns a {@link HttpLoggingInterceptor} with the level given by <b>level</b>.
     *
     * @param level - Logging level for the interceptor.
     * @return New instance of interceptor
     */
    private static HttpLoggingInterceptor buildHttpLoggingInterceptor(
          @NonNull HttpLoggingInterceptor.Level level) {
        HttpLoggingInterceptor loggerInterceptor = new HttpLoggingInterceptor();
        loggerInterceptor.setLevel(level);
        return loggerInterceptor;
    }
}

Create a new NetworkComponent

If you need to satisfy another dependencies or you need to change those provided by WOLMO Networking you can create your own CustomNetworkComponent and add only the wanted modules from WOLMO and your own. For example:

@NetworkingScope
@Component(modules = { JacksonModule.class, OkHttpClientModule.class, NetworkingModule.class })
public interface MyCustomNetworkingComponent {

    RetrofitServices retrofitServices();

    @Component.Builder
    interface Builder {

        @BindsInstance
        Builder baseUrl(String baseUrl);

        @BindsInstance
        Builder okHttpInterceptors(@Nullable Interceptor... interceptors);
	
	...

        MyCustomNetworkingComponent build();
    }
}

Or you can directly add the Modules to your AppComponent if you wish.

Usages

An example instantiation and usage of the Repository class is the following.

Consider the case of an app that needs to load a set of Tasks and wants to fetch them locally first (See Repository.AccessPolicy for other policies). To create the necessary Repository these few lines of code are needed:

mTaskRepository = new Repository(cache, Repository.CACHE_FIRST);

The Repository.AccessPolicy can be omitted if you intend to use the configurable Repository.DEFAULT_ACCESS_POLICY as its access policy.

Note that the creation requires one thing: a TaskCache.

We won't dwell in the details of what a TaskCache should be. The advantage is that it can be anything the users of the library consider a cache. It can be implemented atop of memory, SQLite database, a faster web service, etc.

On the other hand, a QueryStrategy is a mechanism that defines what to do in case the Repository determines it should either readLocalSource or consumeRemoteSource.

public class TaskQueryStrategy extends Repository.QueryStrategy<List<Task>, TaskCache> {

    @Nullable
    @Override
    public List<Task> readLocalSource(@NonNull TaskCache cache) {
        // Read from cache
        // Important to notice that it should return null when there's a cache miss.
     }

     @Override
     public void consumeRemoteSource(@NonNull List<Task> tasks, @NonNull TaskCache cache) {
         // Save tasks to cache
     }
     
}

Finally, the last step is to query the newly created Repository<Task, TaskCache> to then act on the Tasks information retrieved.

There are 2 flavors of Repository#query, each offering an overload for passing an AccessPolicy to use for that particular query.

  • Executing the query logic immediately:
mTaskRepository.query(
    getService(TaskService.class).fetchTasks(),
    new TaskQueryStrategy() {
        // Implementation
    },
    new IRepositoryCallback<List<Task>> {
        // Callback Implementation
    });

Information retrieval is notified through IRepositoryCallback#onSuccess(T) and errors through IRepositoryCallback#onError(Throwable). The user can then execute the required behaviour after said events. Whether it fetched the information from network, memory, disk or any other source is transparent.

  • Getting a hold of a Repository.Query object:
Query<List<Task>> tasksQuery = mTaskRepository.query(
    getService(TaskService.class).fetchTasks(),
    new TaskQueryStrategy() {
        // Implementation
    });

The Repository.Query holds the logic of the query ready to be Repository.Query#run. This class implements Runnable, which allows integration with several other APIs.

This allows the users of the class to do:

tasksQuery
    .onSuccess((List<Task> tasks) -> {// Implement})
    .onError((Throwable error) -> {// Implement})
    .run();

The SAM interfaces for Repository.Query#onSuccess and Repository.Query#onError are Consumer<T> and Consumer<Throwable> respectively.

Important note: We are using a custom Customer class. When the Java 8 functional interfaces are supported everywhere officially, the implementation will use those.

The library also offers a tool to handle the 'dirty' cache common problem. Users can create TimeResolveQueryStrategy which already handles that case for them. Instances of this class are created with a delta time that is the time left for the local data to be considered 'clean'. When that amount of time has passed since the last remote data fetch, the TimeResolveQueryStrategy forces the remote query.

Dependencies

  1. WOLMO CORE
  2. Retrofit
  3. OkHTTP3
  4. Joda Time
  5. Dagger

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push your branch (git push origin my-new-feature)
  5. Create a new Pull Request

About

This project was written by Wolox and is maintained by:

Wolox

License

WOLMO NETWORKING is available under the MIT license.

Copyright (c) Wolox S.A

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

wolmo-networking-android's People

Contributors

dylan-muszel avatar epintos avatar juanignaciomolina avatar lmiotti avatar spicycactuar avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

wolmo-networking-android's Issues

Creating the `QueryStrategy` in the `Repository` construction forces saving state

Summary

Let's say we have a Note class as follows:

public final class Note {

    private final DateTime createdAt;
    private final String content;

    /** Constructors & getters **/
}

And the user wants to save the Note either locally or to network (through a Call<Note>). The scenario is shown below:

public final class SaveNoteInteractor {

    private final NoteService mNoteService;
    private final Repository<Note, NoteCache> mSaveNoteRepository;

    private DateTime mNoteCreatedAt;
    private String mNoteContent;

    public SaveNoteInteractor(@NonNull NoteCache noteCache, @NonNull NoteService noteService) {
        mNoteService = noteService;
        mSaveNoteRepository = new Repository.Builder<>(noteCache,
                new Repository.QueryStrategy<Note, NoteCache>() {
                    @Nullable
                    @Override
                    public Note read(@NonNull NoteCache cache) {
                        // Here i use the saved information to create a Note
                        Note noteToSave = new Note(mNoteCreatedAt, mNoteContent);
                        cache.save(noteToSave);

                        return noteToSave;
                    }
                    
                    /** invalidate & save methods **/
                })..withDefaultAccessPolicy(Repository.CACHE_FIRST).build();
    }

    public void addTask(@NonNull DateTime noteCreatedAt, @NonNull String noteContent, @NonNull RepositoryCallback<Note> noteSavedCallback) {
        mNoteCreatedAt = dueDate;
        mNoteContent = content;
        mSaveNoteRespository.query(mNoteService.saveNote(new SaveNoteRequest(mNoteCreatedAt, mNoteContent), noteSavedCallback));
    }

}

The user has to assign mNoteCreatedAt and mNoteContent which is supposed to be temporal data. What i'd like is for it to be used in an anonymous class, instantiated in the addTask method in the example, so it can reference the parameters directly and avoid having them as fields.

Provide a mechanism to use `Repository<Void, C>`

Summary

Sometimes a user may want to make a change to data locally or in API, but the 'response' contains no data.

The issue is the following:

  • User instantiates a Repository<Void, C>.
  • It has to provide a QueryStrategy<Void, C>.
  • QueryStrategy#read would return Void.
  • It can't return null since Repository would interpret it as a cache miss. Void cannot be instantiated.
  • The user is forced to return something, Object for example. In consequence, it's forced to change the Call type parameter from Void to Object yielding a Call<Void>.

Provide a mechanism to inspect Network conditions

In Onboarding Digital we used a custom class NetworkReporter to inspect properties of the network such as:

  • Is the device connected to any network?
  • Is device connected to WiFi?
  • Which mobile network category is the device connected to (2G, 3G, 4G)?

We should include said module in this repository since it's a common use case that we are not handling right now.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.