简体   繁体   中英

How to fix: The first time OnCreate() is called, I can't retrieve data from Retrofit Concrete Class

I'm retrieving data from a REST API using Retrofit. First I tried everything on the MainActivity and it worked great. Then, I moved some methods to a singleton pattern ClientApi class (is this the right approach? I think it is but I didn't do it properly) Now, I can't see the results on the first OnCreate() method, all I see is "null". Finally, if I wait 1 second and rotate the phone to change to landscape (so onCreate() is called again) it works.

public class MainActivity extends AppCompatActivity {
    //UI components
    TextView textViewHello;
    //variables
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textViewHello = findViewById(R.id.text_hello);
        ClientApi clientApi = ClientApi.getInstance();
        Client client = clientApi.getClient(2);
        String clientString = client.toString();
        textViewHello.setText(clientString);
    }
}


public class ClientApi {

    private static final String TAG = "ClientApi";
    private static final String API_BASE_URL = "https://my-json-server.typicode.com/";
    private ClientsService clientsService;
    public Client client = new Client();

    private static ClientApi instance = null;

    private ClientApi() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(API_BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        clientsService = retrofit.create(ClientsService.class);
    }

    public static ClientApi getInstance() {
        if (instance == null) {
            instance = new ClientApi();
        }
        return instance;
    }

    public Client getClient(int clientId){
        getClient1(clientId);
        return client;
    }

    private void getClient1(int clientId) {
        Call<Client> call = clientsService.getClient(clientId);
        call.enqueue(new Callback<Client>() {
            @Override
            public void onResponse(Call<Client> call, Response<Client> response) {
                if (response.isSuccessful()) {
                    client = response.body();
                    Log.d(TAG, "onResponse: response successfull, client: " + client);
                } else {
                    Log.d(TAG, "onResponse: response not successfull: " + response.code());
                }
            }

            @Override
            public void onFailure(Call<Client> call, Throwable t) {
                Log.d(TAG, "onFailure: " + t.getMessage());
            }
        });
    }
}

The expected result is to see the information about one client on the first time the app is started. But I can't see it until I change to landscape or portrait.

When clientApi.getClient(2) is called the immediate value returned is a fresh instance of client created in your class ClientApi, so when OnCreate is called again the data is available because your api is finished

public Client client = new Client();

If method private void getClient1(int clientId) {...} is asynchronous you need to pass a listener to receive the result of your API has already returned the data

Something like that

// create a interface to your response
public interface ApiResponse {
   onResponse(Object response);
}
//update your getClient method
public void getClient(int clientId, ApiResponse apiResponse){
    getClient1(clientId, apiResponse);
    return client;
}
//update your getClient1 method and call the listener
private void getClient1(int clientId, ApiResponse apiResponse) {
        Call<Client> call = clientsService.getClient(clientId);
        call.enqueue(new Callback<Client>() {
            @Override
            public void onResponse(Call<Client> call, Response<Client> response) {
                if (response.isSuccessful()) {
                    client = response.body();
                    // Call the listener
                    // apiResponse.onResponse(client)
                    Log.d(TAG, "onResponse: response successfull, client: " + client);
                } else {
                    Log.d(TAG, "onResponse: response not successfull: " + response.code());
                }
            }

            @Override
            public void onFailure(Call<Client> call, Throwable t) {
                Log.d(TAG, "onFailure: " + t.getMessage());
            }
        });
    }

Then in your MainActivity call your api method

Client client = clientApi.getClient(2, new ApiResponse() {
    @Override()
    public void onResponse(Object response) {
       // response is your client data
    }

});

This is because you haven't realize that the Retrofit is working asynchronous when using call.enqueue() . When the first time you're calling the following:

    ClientApi clientApi = ClientApi.getInstance();
    Client client = clientApi.getClient(2);
    String clientString = client.toString();
    textViewHello.setText(clientString);

client variable in ClientApi isn't filled yet with the data from Retrofit call. But when you waiting 1 second then rotate the device, your client variable is already filled with data from the previous Retrofit call. So, you actually didn't get the current data in your TextView.

You need to either using a callback or pass your TextView to your ClientApi instance.

You are fetching your client asynchronousely but you are processing the result as if it were a synchronous call. client will hold the value you want after onResponse is called. I can suggest you create a private listener that will notify you when the value of client changes.

For that, I could proceed this way:

public interface OnClientFetchedListener{
  void onClientFetched(Client client);
  void onError(String errorMessage);
}

Then create a member in ClientApi of type OnClientFetchedListener and add a setter. Then call invoke an appropriate method when on success or on error. That can be achieved this way:

public class ClientApi {

    private static final String TAG = "ClientApi";
    private static final String API_BASE_URL = "https://my-json-server.typicode.com/";
    private ClientsService clientsService;
    public Client client = new Client();

    private static ClientApi instance = null;

    //our listener is just here
    private OnClientFetchedListener listener;

    //our setter is just here
    public void setListener(OnClientFetchedListener listener){
      this.listener = listener;
    }

    private ClientApi() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(API_BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        clientsService = retrofit.create(ClientsService.class);
    }

    public static ClientApi getInstance() {
        if (instance == null) {
            instance = new ClientApi();
        }
        return instance;
    }

    public Client getClient(int clientId){
        getClient1(clientId);
        return client;
    }

    private void getClient1(int clientId) {
        Call<Client> call = clientsService.getClient(clientId);
        call.enqueue(new Callback<Client>() {
            @Override
            public void onResponse(Call<Client> call, Response<Client> response) {
                if (response.isSuccessful()) {
                    client = response.body();

                  //invoke an appropriate method when on success
                  if(listener!=null)
                  {listener.onClientFetched(client);
                  }
                    Log.d(TAG, "onResponse: response successfull, client: " + client);
                } else {
                    Log.d(TAG, "onResponse: response not successfull: " + response.code());
                }
            }

            @Override
            public void onFailure(Call<Client> call, Throwable t) {
                Log.d(TAG, "onFailure: " + t.getMessage());
                //invoke an appropriate method when on failure
                  if(listener!=null)
                  {listener.onError(t.getMessage());
                  }
            }
        });
    }
}

Then after, inside the onCreate set a listener to the ClientApi object and listen to events. You can make your activity/fragment implement the interface if you want. The first way can be achieved this way:

public class MainActivity extends AppCompatActivity {
    //UI components
    TextView textViewHello;
    //variables
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textViewHello = findViewById(R.id.text_hello);
        ClientApi clientApi = ClientApi.getInstance();

        //let's set our listener
        clientApi.setListener(new OnClientFetchedListener(){
        @Override
        public void onClientFetched(Client client)
        {
          if(client!=null)
          {
            String clientString = client.toString();
        textViewHello.setText(clientString);
          }
        }

        @Override
        public void onError(String errorMessage)
        {
        //log the error or something else
        }
        });


    }
}

Any way, this is how I could this about that. There are too many solutions...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM