I'm new to Android development, and am trying to use a good pattern for accessing my data from a web service using ViewModel, Retrofit, LiveData. Looking for feedback on this pattern.
I went through a few iterations and online guides, and came up with this
From the Activity:
LiveData<TicklerResponse> ticklerLiveData =
ticklerViewModel.search(ticklerSearchObject);
ticklerLiveData.observe(this, new Observer<TicklerResponse>() {
#Override
public void onChanged(#Nullable TicklerResponse ticklerResponse) {
Log.d(TAG, "onChanged: ticklerResponse size = " + ticklerResponse.getContent().size());
}
});
The ViewModel
public LiveData<TicklerResponse> search(TicklerSearchObject ticklerSearchObject) {
TicklerRepository ticklerRepository = TicklerRepository.getInstance();
String header = OscarOAuthContainer.getInstance()
.generateAuthorizationHeader(Verb.POST,"/tickler/search?startIndex=0&limit=1000");
MutableLiveData<TicklerResponse> liveData = new MutableLiveData<TicklerResponse>();
ticklerRepository.search(header,liveData, ticklerSearchObject);
return liveData;
}
The Repository
public void search(final String authHeader, final MutableLiveData<TicklerResponse> ticklerLiveData, TicklerSearchObject ticklerSearchObj) {
Log.d(TAG, "search: called");
Call<TicklerResponse> call = api.search(authHeader,0,1000,ticklerSearchObj);
call.enqueue(new Callback<TicklerResponse>() {
#Override
public void onResponse(Call<TicklerResponse> call, Response<TicklerResponse> response) {
if(response.isSuccessful()){
Log.d(TAG, "onResponse: successful response for getProvider");
TicklerResponse result = response.body();
ticklerLiveData.setValue(result);
} else {
Log.d(TAG, "onResponse: NOT SUCCESSFUL");
}
}
#Override
public void onFailure(Call<TicklerResponse> call, Throwable t) {
t.printStackTrace();
}
});
}
It seems to work, but I'm concerned if I'm using the LiveData structure correctly in how it's being instantiated/passed around.
Related
I have a Posts Feed (Something like Facebook News Feed) Activity and in it's layout I have an EditText, share Button at the top and a RecyclerView below it. The RecyclerView shows the Posts of all users by getting all the Posts from Server and binding data through Adapter in OnCreate Method.
When a User post something by typing some text in EditText and hit share Button, the data is sent to Server (I am using Retrofit) and upon Successful Response from Server I call the same function which call in OnCreate Method to show all Posts to update the RecyclerView.
The Problem I am facing is that the data is posted to Server, but the Layout only gets updated only when I press the back button to Hide/Close the Keyboard after typing or Show/Open the Keyboard by tapping the EditText.
Following is Some of the Code for better understanding:
Here I Send Request to Server when User Post something:
Call<CreatePost> call = mAPIService.sharePost(apiKey, post_description);
call.enqueue(new Callback<CreatePost>() {
#Override
public void onResponse(#NonNull Call<CreatePost> call, #NonNull Response<CreatePost> response) {
boolean error = response.body().getError();
if (!error) {
displayFeedPosts();
}
}
#Override
public void onFailure(#NonNull Call<CreatePost> call, #NonNull Throwable t) {
Log.d(TAG, "Error: " + t.getMessage());
}
});
Here Is displayFeedPosts Method:
private void displayFeedPosts() {
Call<FeedPosts> call = mAPIService.displayFeedPosts(apiKey);
call.enqueue(new Callback<FeedPosts>() {
#Override
public void onResponse(#NonNull Call<FeedPosts> call, #NonNull Response<FeedPosts> response) {
boolean error = response.body().getError();
if (!error) {
ArrayList<Post> feedPosts = response.body().getPosts();
for (Post post : feedPosts) {
mTripFeedPostUserNames.add(post.getFirstName() + " " + post.getLastName());
mTripFeedPostTime.add(post.getPostDatetime());
mTripFeedPostContent.add(post.getPostDescription());
}
}
}
#Override
public void onFailure(#NonNull Call<FeedPosts> call, #NonNull Throwable t) {
Log.d(TAG, "Error: " + t.getMessage());
}
});
initRecyclerView();
}
private void initRecyclerView() {
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true);
RecyclerView recyclerView = findViewById(R.id.trip_feed_recycler_view);
recyclerView.setNestedScrollingEnabled(false);
recyclerView.setLayoutManager(layoutManager);
mTripFeedPostRecyclerViewAdapter = new TripFeedPostRecyclerViewAdapter(this, mTripFeedPostProfileImages, mTripFeedPostUserNames, mTripFeedPostTime, mTripFeedPostContent, mTripFeedPostImages, mTripFeedPostID);
recyclerView.setAdapter(mTripFeedPostRecyclerViewAdapter);
}
PS: I am new to Android, I apologise if I have done things the wrong way. Your suggestions are Welcomed.
Note: The same Question has been asked regarding ListView here and here but it doesn't solved my problem
I solved the above problem with the help of Arsalan Khan Answer.
Actually the initRecyclerView() was not executing outside the onResponse() method. Including it inside the onResponse() method and using notifyDataSetChanged() instead of initRecyclerView() solved the problem.
The solution to data being populated twice I solved it by modifying the displayFeedPosts() in the following way:
private void updateFeedPosts() {
Call<FeedPosts> call = mAPIService.displayFeedPosts(apiKey);
call.enqueue(new Callback<FeedPosts>() {
#Override
public void onResponse(#NonNull Call<FeedPosts> call, #NonNull Response<FeedPosts> response) {
boolean error = response.body().getError();
if (!error) {
mTripFeedPostProfileImages.clear();
mTripFeedPostUserNames.clear();
mTripFeedPostTime.clear();
mTripFeedPostContent.clear();
mTripFeedPostImages.clear();
mTripFeedPostID.clear();
mTripFeedPostRecyclerViewAdapter.notifyDataSetChanged();
ArrayList<Post> feedPosts = response.body().getPosts();
for (Post post : feedPosts) {
mTripFeedPostProfileImages.add(post.getProfilePicture());
mTripFeedPostUserNames.add(post.getFirstName() + " " + post.getLastName());
mTripFeedPostTime.add(post.getPostDatetime());
mTripFeedPostContent.add(post.getPostDescription());
mTripFeedPostImages.add(post.getPostImagePath());
mTripFeedPostID.add(post.getPostTripfeedId());
}
mTripFeedPostRecyclerViewAdapter.notifyDataSetChanged();
}
}
#Override
public void onFailure(#NonNull Call<FeedPosts> call, #NonNull Throwable t) {
Log.d(TAG, "Error: " + t.getMessage());
}
});
}
I cleared the Adapter Data before populating it again with updated data.
I hope my answer will help someone having the same problem.
PS: I know I am not using my Adapter Model in correct way, I can use only one ArrayList for all the values, I'll modify it later right now I am focusing on the main functions.
should call initRecyclerView here:
private void displayFeedPosts() {
Call<FeedPosts> call = mAPIService.displayFeedPosts(apiKey);
call.enqueue(new Callback<FeedPosts>() {
#Override
public void onResponse(#NonNull Call<FeedPosts> call, #NonNull Response<FeedPosts> response) {
boolean error = response.body().getError();
if (!error) {
ArrayList<Post> feedPosts = response.body().getPosts();
for (Post post : feedPosts) {
mTripFeedPostUserNames.add(post.getFirstName() + " " + post.getLastName());
mTripFeedPostTime.add(post.getPostDatetime());
mTripFeedPostContent.add(post.getPostDescription());
}
initRecyclerView();
}
}
#Override
public void onFailure(#NonNull Call<FeedPosts> call, #NonNull Throwable t) {
Log.d(TAG, "Error: " + t.getMessage());
}
});
}
private void initRecyclerView() {
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true);
RecyclerView recyclerView = findViewById(R.id.trip_feed_recycler_view);
recyclerView.setNestedScrollingEnabled(false);
recyclerView.setLayoutManager(layoutManager);
mTripFeedPostRecyclerViewAdapter = new TripFeedPostRecyclerViewAdapter(this, mTripFeedPostProfileImages, mTripFeedPostUserNames, mTripFeedPostTime, mTripFeedPostContent, mTripFeedPostImages, mTripFeedPostID);
recyclerView.setAdapter(mTripFeedPostRecyclerViewAdapter);
mTripFeedPostRecyclerViewAdapter.notifyDataSetChanged();
}
I'm trying to learn Retrofit making simple weather app and I have some problem. I made two methods to display data in Activity and method does not work outside onCreate, but second one in works. In Android Profiler I can see JSON logs, but still I don't see anything in Activity. What's the problem?
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(WeatherAPI_Interface.API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
WeatherAPI_Interface weatherAPIInterface = retrofit.create(WeatherAPI_Interface.class);
latitude = 37.8267;
longitude = -122.4233;
Call<Currently> call = weatherAPIInterface.getWeather(latitude, longitude);
call.enqueue(new Callback<Currently>() {
#Override
public void onResponse(#NonNull Call<Currently> call, #NonNull Response<Currently> response) {
temp_textview.setText(String.valueOf(response.body().getCurrently().getTemperature()));
city_textview.setText(response.body().getTimezone());
image_View.setImageResource(Integer.parseInt(response.body().getCurrently().getIcon()));
}
#Override
public void onFailure(#NonNull Call<Currently> call, #NonNull Throwable t) {
}
});
}
#Override
public void getCurrentWeather(final Currently currently) {
latitude = 37.8267;
longitude = -122.4233;
NetworkClient.getInstance().getWeather(latitude, longitude).enqueue(new Callback<Currently>() {
#Override
public void onResponse(#NonNull Call<Currently> call, #NonNull Response<Currently> response) {
weather_textview.setText((CharSequence) response.body().getCurrently().getSummary());
pressure_textview.setText((CharSequence) response.body().getDaily().getData());
temp_textview.setText(String.valueOf(currently.getCurrently().getTemperature()));
Log.d(TAG, "City" + currently.getLatitude());
}
#Override
public void onFailure(Call<Currently> call, Throwable t) {
Log.d(TAG, "Failed" + t.getMessage());
}
});
}
public class NetworkClient {
private static WeatherAPI_Interface service;
public static WeatherAPI_Interface getInstance() {
if (service == null) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(WeatherAPI_Interface.API_URL)
.build();
service = retrofit.create(WeatherAPI_Interface.class);
return service;
}
else {
return service;
}
}
}
You didn't call the function how can it work?
when the activity starts it'll run the code in OnCreate ( and any call in it) only, so to run any function outside it you need to call it.
I have this project and wish to get all names from table like "1" is the first name how code this for all names
public void capturarPersonagens(View view) {
//-- Retrofit
callPersonagem = service.getPersonagem("1");
callPersonagem.enqueue(new Callback<Personagem>() {
#Override
public void onResponse(Call<Personagem> call, Response<Personagem> response) {
if (response.isSuccessful()) {
if (!call.isCanceled()) {
Log.i("Personagem",response.body().getName());
}
}
}
}
}
Best Regards
Pedro Duarte
You are missing a new method in your Service interface:
The api endpoint people/?format=json depends on the actual implementation of your webservice.
public interface Service {
#GET("people/{id}/?format=json")
Call<Personagem> getPersonagem(#Path("id") String alacazam);
#GET("people/?format=json")
Call<ArrayList<Personagem>> getPersonagens();
}
Then, you may consume it the same way you did with getPersonagem:
public void capturarPersonagens(View view) {
//-- Retrofit
callPersonagens = service.getPersonagens();
callPersonagens.enqueue(new Callback<ArrayList<Personagem>>() {
#Override
public void onResponse(Call<ArrayList<Personagem>> call, Response<ArrayList<Personagem>> response) {
if (response.isSuccessful()) {
if (!call.isCanceled()) {
for(Personagem p:response.body()){
Log.i("Personagem",p.getName());
}
}
}
}
#Override
public void onFailure(Call<ArrayList<Personagem>> call, Throwable t) {
Log.e("SW", "" + t.getMessage());
}
});
}
I am using retrofit:2.1.0 and I am trying to save the response returned to my own POJO (UserProfile) but I can't seem to access the POJO, I assign the response to, outside of the callbacks.
So, in this call below, I want to have access to UserProfile outside of this call.
//adding `UserProfile userProfile;` outside of Call didn't help either
call.enqueue(new Callback<UserProfile>() {
#Override
public void onResponse(Call<UserProfile> call, Response<UserProfile> response) {
if (response.isSuccessful()) {
UserProfile userProfile = response.body();
}
}
#Override
public void onFailure(Call<UserProfile> call, Throwable t) {
//do something
}
});
//here userProfile is null and hence can't get status
Log.d(TAG, "Status outside of Call is: " + userProfile.getStatus());
New approach, same result
private List<UserProfile> userProfileList = new ArrayList<>();
call.enqueue(new Callback<UserProfile>() {
#Override
public void onResponse(Call<UserProfile> call, Response<UserProfile> response) {
if (response.isSuccessful()) {
UserProfile userProfile = response.body();
userProfileList.add(userProfile);
}
}
#Override
public void onFailure(Call<UserProfile> call, Throwable t) {
//do something
}
});
And this one gets fired even before callback and hence is null
if(userProfileList.size() > 0) {
for(UserProfile userProfile : userProfileList) {
Log.d(TAG, "Status is: " + userProfile.getStatus());
}
} else {
Log.d(TAG, "YakkerProfileList is NULL");
}
I added a setter in onResponse() which allowed me to use the response object elsewhere in the code like so:
#Override
public void onResponse(Call<UserProfile> call, Response<UserProfile> response) {
if (response.isSuccessful()) {
UserProfile userProfile = response.body();
//elsewhere in the code, I can read the value from this method
setUserProfile(userProfile.getStatus());
}
}
I am using Retrofit 2.0 to make api calls with nesting multiple requests. All api's works fine individually.
But when i nested all retrofit, First request execute perfectly but after that when i register second request it's not callback in enqueue method (i.e. it's directly returning null without inserting enqueue's inner methods like onResponse, onFailure)
My Code :-
public class Main2Activity extends AppCompatActivity {
Gson gson;
JSONObject jsonResult=null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
gson=new GsonBuilder().create();
firstRequest(); //-- First retrofit request
}
private void firstRequest() {
Retrofit retrofit=new Retrofit.Builder().baseUrl(getResources().getString(R.string.Api_Url)).addConverterFactory(GsonConverterFactory.create(gson)).build();
CityRetailsApi service = retrofit.create(CityRetailsApi.class);
Call call_first= service.getMainCatFlag();
call_first.enqueue(new Callback() {
#Override
public void onResponse(Call call, Response response) {
Log.d("MainActivity", "Status Code = " + response.code());
if (response.isSuccessful()){
MainCatFlag result = (MainCatFlag) response.body(); //-- Get First request response
JSONObject json2nd = secondRequest(); //-- Second request
}
}
#Override
public void onFailure(Call call, Throwable t) {
Log.d("MainActivity", "Error");
}
});
}
private JSONObject secondRequest() {
try {
Retrofit retrofit=new Retrofit.Builder().baseUrl(getResources().getString(R.string.Api_Url)).addConverterFactory(GsonConverterFactory.create(gson)).build();
CityRetailsApi service = retrofit.create(CityRetailsApi.class);
Call call_second= service.getMainCat();
call_second.enqueue(new Callback() {
#Override
public void onResponse(Call call2, Response response1) {
Log.d("MainActivity", "Status Code = " + response1.code());
if (response1.isSuccessful()) {
MainCat result = (MainCat) response1.body();
if (result.getSuccess()==1)
{
try {
jsonResult= new JSONObject(new Gson().toJson(result));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
#Override
public void onFailure(Call call, Throwable t) {
Log.d("MainActivity", "Error");
}
});
}catch (Exception e){
Log.d("MainActivity", "Error= " + e);
}
return jsonResult;
}
}
In above code firstRequest() executed correctly and proving response but the secondRequest (inside firstRequest() enqueue method) not working fine. Not showing any error, success message in console. Can any one please help me to override this problem.
If any problem in my code, please let me know.
Thank you in advance.
You made a mistake that when you using retrofit enquene,it's called asynchronously, so you can't get the result outside of the callback method!
So, you should process your result inside the onResponse method like this:
private void secondRequest() {
try {
call_second.enqueue(new Callback() {
#Override
public void onResponse(Call call2, Response response1) {
Log.d("MainActivity", "Status Code = " + response1.code());
if (response1.isSuccessful()) {
MainCat result = (MainCat) response1.body();
if (result.getSuccess()==1)
{
try {
jsonResult= new JSONObject(new Gson().toJson(result));
// process your jsonResult here
...
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
#Override
public void onFailure(Call call, Throwable t) {
Log.d("MainActivity", "Error");
}
});
}catch (Exception e){
Log.d("MainActivity", "Error= " + e);
}
}