How to use GraphQL with Retrofit on Android?

How to use GraphQL with Retrofit on Android?

Have you use GraphQL on Android?

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.

Apollo Android is a GraphQL client that generates Java and Kotlin models from GraphQL queries. These models give you a type-safe API to work with GraphQL servers. Apollo helps you keep your GraphQL query statements together, organized, and easy to access.

You can choose to use Apollo in android when you query is static. But what happen when the query is dynamic query?

We recommend you to use GraphQL with retrofit.

In your manifest to add

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

Your dependencies

// Kotlin Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.4'

//OkHttp
implementation ("com.squareup.okhttp3:okhttp:3.12.12"){
  force = true //API 19 support
}
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.12'

//retrofit
implementation "com.squareup.retrofit2:retrofit:2.7.1"
implementation "com.squareup.retrofit2:converter-scalars:$2.7.1"

Also Java 8 compatibility

android {

    ...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

With the service

import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST

interface GraphQLService {

@Headers("Content-Type: application/json")
@POST("/")
suspend fun postDynamicQuery(@Body body: String): Response
}

you can create a object

import retrofit2.Retrofit
import retrofit2.converter.scalars.ScalarsConverterFactory

object GraphQLInstance {

    private const val BASE_URL: String = "http://192.155.1.55:2000/"

    val graphQLService: GraphQLService by lazy {
        Retrofit
            .Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(ScalarsConverterFactory.create())
            .build().create(GraphQLService::class.java)
    }
}

In the activity you can create this method

private fun post(userId: String){
    val retrofit = GraphQLInstance.graphQLService
    val paramObject = JSONObject()
    paramObject.put("query", "query {users(userid:$userId){username}}")
    GlobalScope.launch {
        try {
            val response = retrofit.postDynamicQuery(paramObject.toString())
            Log.e("response", response.body().toString())
        }catch (e: java.lang.Exception){
            e.printStackTrace()
        }
    }
}

You can check GraphQL with Retrofit on Android in the example in GitHub
GraphQL with Retrofit on Android?

Unit testing retrofit RxJava

Unit testing retrofit RxJava

ReactiveX is a combination of the best ideas from the Observer pattern, the Iterator pattern, and functional programming.

RxAndroid is an extension to RxJava. It providers a scheduler to run code in the main thread of Android. It also provides the ability to create a scheduler that runs on a Android handler class. With this schedulers, you can define an observable which does its work in a background thread, and post our results to the main thread. This allows for example to replace a AsyncTask implementations which RxJava.

The subscriber observes in the main thread
Observable is called outside the main thread

To manage RxJava is not so difficult, When we have to use RxJava in Unit test, We need to create these element:

1. BaseSchedulerProvider (Father)
2. ImmediateSchedulerProvider (Testing)
3. SchedulerProvider (Application)

Android Test, Unit testing retrofit RxJava
You can check my complete example in GitHub

BaseSchedulerProvider :

public interface BaseSchedulerProvider {

    @NonNull
    Scheduler computation();

    @NonNull
    Scheduler io();

    @NonNull
    Scheduler ui();
}

ImmediateSchedulerProvider I use for a test:

public class ImmediateSchedulerProvider implements BaseSchedulerProvider {

    @NonNull
    @Override
    public Scheduler computation() {
        return Schedulers.immediate();
    }

    @NonNull
    @Override
    public Scheduler io() {
        return Schedulers.immediate();
    }

    @NonNull
    @Override
    public Scheduler ui() {
        return Schedulers.immediate();
    }
}

And SchedulerProvider I use in my Presenter

public class SchedulerProvider implements BaseSchedulerProvider {

    // Prevent direct instantiation.
    public SchedulerProvider() {
    }

    @Override
    @NonNull
    public Scheduler computation() {
        return Schedulers.computation();
    }

    @Override
    @NonNull
    public Scheduler io() {
        return Schedulers.io();
    }

    @Override
    @NonNull
    public Scheduler ui() {
        return AndroidSchedulers.mainThread();
    }
}

In my PresenterTest I setUp like this:

public class TopicPresenterTest {

            @Mock
            private RemoteDataSource mRemoteDataSource;

            @Mock
            private TopicContract.View mView;

            private BaseSchedulerProvider mSchedulerProvider;

            TopicPresenter mPresenter;

            List<Topics> mList;

            @Before
            public void setup() {
            MockitoAnnotations.initMocks(this);

                    Topics topics = new Topics(1, "Discern The Beach");
                    Topics topicsTwo = new Topics(2, "Discern The Football Player");
                    mList = new ArrayList<>();
                    mList.add(topics);
                    mList.add(topicsTwo);
//ADD IMMEDIATESCHEDULERPROVIDER !!!!!!!!!!!!!!!
                    mSchedulerProvider = new 
                    ImmediateSchedulerProvider();

                    mPresenter = new TopicPresenter(mRemoteDataSource, mView, mSchedulerProvider);

            }

            @Test
            public void fetchData() {

                when(mRemoteDataSource.getTopicsRx())
                        .thenReturn(rx.Observable.just(mList));

                mThemePresenter.fetch();

                InOrder inOrder = Mockito.inOrder(mView);
                inOrder.verify(mView).setLoadingIndicator(false);
                inOrder.verify(mView).showTopics(mList);

            }

}

And In my Presenter I call to my server with retrofit:

public class TopicPresenter {

    @NonNull
    private BaseSchedulerProvider mSchedulerProvider;

    public TopicPresenter(@NonNull RemoteDataSource remoteDataSource, @NonNull TopicContract.View view) {
                    this.mRemoteDataSource = checkNotNull(remoteDataSource, "remoteDataSource");
                    this.mView = checkNotNull(view, "view cannot be null!");
                    this.mSchedulerProvider = new SchedulerProvider();
 //ADD COMPOSITESUBSCRITPTION !!!!!!     
                    mSubscriptions = new CompositeSubscription();

                    mView.setPresenter(this);
    }

    @Override
    public void fetch() {

        Subscription subscription = mRemoteDataSource.getTopicsRx()
                .subscribeOn(mSchedulerProvider.computation())
                .observeOn(mSchedulerProvider.ui())
                .subscribe((List<Topics> listTopics) -> {
                            mView.setLoadingIndicator(false);
                            mView.showTopics(listTopics);
                        },
                        (Throwable error) -> {
                            try {
                                mView.showError();
                            } catch (Throwable t) {
                                throw new IllegalThreadStateException();
                            }

                        },
                        () -> {
                        });

        mSubscriptions.add(subscription);
    }

    @Override
    public void subscribe() {
        fetch();
    }

    @Override
    public void unSubscribe() {
        mSubscriptions.clear();
    }


}

You can check my complete example in GitHub

how to test the server call with Mockito, Retrofit and RxJava

how to test the server call with Mockito, Retrofit and RxJava

In this example you can learn how to test server call with Mockito and RxJava, We need this elements:

1. Service
2. RemoteDataSource
3. RemoteDataSourceTest

how to test the server call with Mockito, Retrofit and RxJava

Simple Service:

public interface Service {
    String URL_BASE = "https://guessthebeach.herokuapp.com/api/";

    @GET("topics/")
    Observable<List<Topics>> getTopicsRx();

}

For RemoteDataSource

public class RemoteDataSource implements Service {

    private Service api;

    public RemoteDataSource(Retrofit retrofit) {


        this.api = retrofit.create(Service.class);
    }


    @Override
    public Observable<List<Topics>> getTopicsRx() {
        return api.getTopicsRx();
    }
}

The key is MockWebServer from okhttp3.

This library makes it easy to test that your app Does The Right Thing when it makes HTTP and HTTPS calls. It lets you specify which responses to return and then verify that requests were made as expected.

Because it exercises your full HTTP stack, you can be confident that you’re testing everything. You can even copy & paste HTTP responses from your real web server to create representative test cases. Or test that your code survives in awkward-to-reproduce situations like 500 errors or slow-loading responses.

Use MockWebServer the same way that you use mocking frameworks like Mockito:

  1. Script the mocks.
  2. Run application code.
  3. Verify that the expected requests were made.

Here’s a complete example in RemoteDataSourceTest:

 
public class RemoteDataSourceTest {

    List<Topics> mResultList;
    MockWebServer mMockWebServer;
    TestSubscriber<List<Topics>> mSubscriber;

    @Before
    public void setUp() {
        Topics topics = new Topics(1, "Discern The Beach");
        Topics topicsTwo = new Topics(2, "Discern The Football Player");
        mResultList = new ArrayList();
        mResultList.add(topics);
        mResultList.add(topicsTwo);

        mMockWebServer = new MockWebServer();
        mSubscriber = new TestSubscriber<>();
    }

    @Test
    public void serverCallWithError() {
        //Given
        String url = "dfdf/";
        mMockWebServer.enqueue(new MockResponse().setBody(new Gson().toJson(mResultList)));
        Retrofit retrofit = new Retrofit.Builder()
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl(mMockWebServer.url(url))
                .build();
        RemoteDataSource remoteDataSource = new RemoteDataSource(retrofit);

        //When
        remoteDataSource.getTopicsRx().subscribe(mSubscriber);

        //Then
        mSubscriber.assertNoErrors();
        mSubscriber.assertCompleted();
    }

    @Test
    public void severCallWithSuccessful() {
        //Given
        String url = "https://guessthebeach.herokuapp.com/api/";
        mMockWebServer.enqueue(new MockResponse().setBody(new Gson().toJson(mResultList)));
        Retrofit retrofit = new Retrofit.Builder()
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl(mMockWebServer.url(url))
                .build();
        RemoteDataSource remoteDataSource = new RemoteDataSource(retrofit);

        //When
        remoteDataSource.getTopicsRx().subscribe(mSubscriber);

        //Then
        mSubscriber.assertNoErrors();
        mSubscriber.assertCompleted();
    }

}

You can check my example in GitHub

How to use Retrofit library

Retrofit parse JSON of your web service. Use Retrofit library is very simple

Retrofit turns your REST API into a Java interface.

you can download this example from here

You need these librarires.

JSON

Then you can get your JSON, We use this chrome app. Now you can build your model from JSON in this web

You create a new class which name is RestClient

package com.thedeveloperworldisyours.simpleretrofic.webservice;

import retrofit.Callback;
import retrofit.RestAdapter;
import retrofit.RetrofitError;
import retrofit.client.OkClient;
import retrofit.client.Response;
import retrofit.http.GET;
import retrofit.http.Query;
import android.os.Handler;
import android.util.Log;

import com.squareup.okhttp.OkHttpClient;
import com.thedeveloperworldisyours.simpleretrofic.model.Question;
import com.thedeveloperworldisyours.simpleretrofic.utils.Constants;

public class RestClient {

	public interface ClientInterface{
		  @GET("/questions")
		  void getQuestions( @Query("sort") String sort, @Query("site") String site,@Query("pagesize") String pagesize,@Query("page") String page, Callback<Question> callback);
		}

	public static ClientInterface initRestAdapter()
    {
        OkHttpClient client = new OkHttpClient();

        return (ClientInterface) new RestAdapter.Builder()
                .setClient(new OkClient(client))
                .setEndpoint(Constants.URL)
                .build()
                .create(ClientInterface.class);
    }
	public static void GetQuestions(final Handler mHandler) {
		Callback<Question> callback = new Callback<Question>() {

			@Override
			public void failure(RetrofitError resp) {
				Log.v("failure", String.valueOf(resp.getMessage()));
			}

			@Override
			public void success(Question info, Response resp) {
				Log.v("success", String.valueOf(resp.getStatus()));
			}
		};
		RestClient.initRestAdapter().getQuestions(Constants.SORT, Constants.SITE,
				Constants.PAGESIZE, Constants.PAGE, callback);
	}

}

Now in your MainActivity put this:

package com.thedeveloperworldisyours.simpleretrofic;

import java.util.ArrayList;
import java.util.List;

import retrofit.Callback;
import retrofit.RetrofitError;
import retrofit.client.Response;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.HeaderViewListAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.Toast;

import com.thedeveloperworldisyours.simpleretrofic.model.Question;
import com.thedeveloperworldisyours.simpleretrofic.utils.Constants;
import com.thedeveloperworldisyours.simpleretrofic.utils.Utils;
import com.thedeveloperworldisyours.simpleretrofic.webservice.RestClient;

public class MainActivity extends ActionBarActivity {

	List<String> mListTitle = new ArrayList<String>();
	private ListView mListView;

		protected ListView getListView() {
		    if (mListView == null) {
		        mListView = (ListView) findViewById(android.R.id.list);
		    }
		    return mListView;
		}

		protected void setListAdapter(ListAdapter adapter) {
		    getListView().setAdapter(adapter);
		}

		protected ListAdapter getListAdapter() {
		    ListAdapter adapter = getListView().getAdapter();
		    if (adapter instanceof HeaderViewListAdapter) {
		        return ((HeaderViewListAdapter)adapter).getWrappedAdapter();
		    } else {
		        return adapter;
		    }
		}

	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		setContentView(R.layout.activity_main);
		getQuestionCheckInternet();

	}

	@Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        MenuInflater menuInflater = getMenuInflater();
        menuInflater.inflate(R.menu.main, menu);
        return true;
    }
	@Override
    public boolean onOptionsItemSelected(MenuItem item)
    {

        switch (item.getItemId())
        {
        case R.id.action_refresh:
        	refreshList();
        	if(Utils.isOnline(MainActivity.this)){
    			Toast.makeText(this, R.string.action_refresh, Toast.LENGTH_SHORT).show();
    		}
            return true;

        default:
            return super.onOptionsItemSelected(item);
        }
        }
	public void refreshList(){
		mListTitle.clear();
		getQuestionCheckInternet();
		createList();

	}

	public void getQuestionCheckInternet(){
		if(Utils.isOnline(MainActivity.this)){
			getQuestions();
		}else{
			Toast.makeText(this, R.string.info_offline, Toast.LENGTH_LONG).show();
		}
	}

	protected void onListItemClick(ListView lv, View v, int position, long id) {
	    getListView().getOnItemClickListener().onItemClick(lv, v, position, id);
	    String item = (String) getListAdapter().getItem(position);
		Toast.makeText(this, item, Toast.LENGTH_LONG).show();
	}

	public void getQuestions() {
		Callback<Question> callback = new Callback<Question>() {

			@Override
			public void failure(RetrofitError resp) {
				Log.v("failure", String.valueOf(resp.getMessage()));
			}

			@Override
			public void success(Question info, Response resp) {
				Log.v("success", String.valueOf(resp.getStatus()));
				getTitle(info);
	            createList();
			}
		};
		RestClient.initRestAdapter().getQuestions(Constants.SORT, Constants.SITE,
				Constants.PAGESIZE, Constants.PAGE, callback);
	}

	public List<String> getTitle(Question info) {
		for (int i = 0; i < info.getItems().size(); i++) {
			mListTitle.add(info.getItems().get(i).getTitle());
		}
		return mListTitle;
	}
	public void createList(){

		// use your custom layout
		ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
		        android.R.layout.simple_list_item_1,mListTitle);
		setListAdapter(adapter);
	}
}

you can download this example from here