I am trying to make an API call with Retrofit 2.9.0 and use the response in a Jetpack Compose activity. The problem is that the call repeats to infinite and updates the UI on every frame. I have never used the retrofit library before so I'm sorry if the code is messy. I will attach a GIF with how the app behaves and code snippets for everything. Thanks for reading this and wanting to help. <3
What is happening in the app
Here is the class that makes the call to the API:
public class ApiHandler {
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.quotable.io/").build();
ApiInterface apiInterface = retrofit.create(ApiInterface.class);
Call<ResponseBody> call = apiInterface.getData();
public void getQuoteRaw()
{
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
MainActivityKt.getModel().setQuoteInfo(response.body().string());
} catch (IOException e)
{
e.printStackTrace();
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
System.out.println("ERROR");
}
});
}
The Interface I use to define the getData() method:
public interface ApiInterface {
#GET("random")
Call<ResponseBody> getData();
The ViewModel class for the Compose Activity:
class MainViewModel: ViewModel() {
var quoteInfo by mutableStateOf("")
The Jetpack Compose activity:
val model = MainViewModel()
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
QuoteappTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Greeting()
}
}
}
}
}
#Composable
fun Greeting(viewModel: MainViewModel = model) {
val apihandle = ApiHandler()
apihandle.getQuoteRaw()
Text(text = viewModel.quoteInfo)
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
QuoteappTheme {
Greeting()
}
}
Related
I am using MVVM pattern in my app I have separate repository class for network operations. In repository class I am getting response from the server. How can I show Toast message send from the server in my main activity.
Below is my code:
Repository.java
public class MyRepository {
MutableLiveData<List<Facts>> mutableLiveData = new MutableLiveData<>();
Application application;
public MyRepository(Application application) {
this.application = application;
}
public MutableLiveData<List<Facts>> getMutableLiveData(){
Retrofit retrofit = RetrofitClient.getInstance();
ApiService apiService = retrofit.create(ApiService.class);
apiService.getFacts().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Facts>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(List<Facts> facts) {
if(facts.size() > 0 && facts != null){
mutableLiveData.setValue(facts);
}
}
#Override
public void onError(Throwable e) {
TastyToast.makeText(application,e.getMessage(),TastyToast.LENGTH_SHORT,
TastyToast.ERROR).show();
}
#Override
public void onComplete() {
}
});
return mutableLiveData;
}
}
FactsViewModel.java
public class FactsViewModel extends AndroidViewModel {
MyRepository repo;
public FactsViewModel(#NonNull Application application) {
super(application);
repo = new MyRepository(application);
}
public LiveData<List<Facts>> getAllFacts(){
return repo.getMutableLiveData();
}
}
MainActivity.java
private void myFacts(){
FactsViewModel viewModel = new ViewModelProvider(this).get(FactsViewModel.class);
viewModel.getAllFacts().observe(this, new Observer<List<Facts>>() {
#Override
public void onChanged(List<Facts> facts) {
adapter = new FactsAdapter(facts,getActivity());
recycle.setAdapter(adapter);
}
});
}
How can I show error toast messages in MainActivity?
To implement that you firstly need to create a class which has the status of the response ,
Loading which is before the fetching of the data and there you can set progress bar to visible then on success you would set the data to your adapter and right after your hide your progress bar and in the on failure one , you show the toast message error
This is the generic class
class AuthResource<T>(
var authStatus : AuthStatus? = null,
var data : T,
var msg : String? = null
)
fun <T> success(#Nullable data: T): AuthResource<T> {
return AuthResource(
AuthStatus.Success,
data,
null
)
}
fun <T> Error(#NonNull msg: String?, #Nullable data: T) : AuthResource<T>? {
return AuthResource(
AuthStatus.ERROR,
data,
msg
)
}
fun <T> loading(#Nullable data: T): AuthResource<T>? {
return AuthResource(
AuthStatus.LOADING,
data,
null
)
}
enum class AuthStatus {
Success, ERROR, LOADING
}
This is my view model where i implement the authResource with the api response
class MainViewModel #Inject constructor( private var webAuth: WebAuth,
private var favFoodDao: FavFoodDao,
private var application: Application) : ViewModel() {
/// you have to create MediatorLiveData with authresource which contains your modelclass
private var mediatorLiveData = MediatorLiveData<AuthResource<WrapLatestMeals>>()
///Here you return a livedata object
fun ObserverCountries(): LiveData<AuthResource<WrapCountries>> {
var liveData = LiveDataReactiveStreams.fromPublisher(
webAuth.getCountries()
///onerrorreturn , rxjava operator which returns error in case
///of response failure
.onErrorReturn(object : Function<Throwable, WrapCountries> {
override fun apply(t: Throwable): WrapCountries {
var country = WrapCountries()
return country
}
})
.map(object : Function<WrapCountries,
AuthResource<WrapCountries>> {
override fun apply(t: WrapCountries):
AuthResource<WrapCountries> {
if(t.meals.isNullOrEmpty())
{
return Error(
"Error",
t
)!!
}
return success(t)
}
})
.subscribeOn(Schedulers.io())
)
//add that data to mediatorLivedata
mediatorLiveDataCountries.addSource(liveData, Observer {
mediatorLiveDataCountries.postValue(it)
mediatorLiveDataCountries.removeSource(liveData)
})
return mediatorLiveDataCountries
}
This is how you handle the status in your MainActivity
mainViewModel = ViewModelProvider(this,provider)[MainViewModel::class.java]
mainViewModel.ObserverCountries().observe(viewLifecycleOwner, Observer {
when(it.authStatus) {
AuthStatus.LOADING -> /// here you show progressbar in response pre-fetch
{
countriesFragmentBinding.countryprogress.show()
}
AuthStatus.Success -> { // here you update your ui
countriesAdapter = CountriesAdapter(it.data.meals!!,
requireContext())
countriesFragmentBinding.recyclercountries.adapter = countriesAdapter
countriesAdapter!!.deleteCategory(23)
countriesFragmentBinding.countryprogress.hide()
}
AuthStatus.ERROR -> // here you hide your progressbar and show your toast
{
countriesFragmentBinding.countryprogress.hide()
ToastyError(requireContext(),getString(R.string.errorretreivingdata))
}
}
})
return countriesFragmentBinding.root
}
}
In my first android project i made an onClick event to call a function:
public void doSomething(View v) {
String result = authenticate();
[...]
}
This function calls the method:
private String authenticate() {
OkHttpClient client = new OkHttpClient();
[...]
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
[...]
}
#Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String responseString = response.body().string();
try {
JSONObject responseObject = new JSONObject(responseString);
String responseObjectAccessToken = responseObject.getString("accesstoken");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
});
}
now i would like to return the responseObjectAccessToken to my doSomething function. A detailed explanation would be great since I am new to Java and Android Studio.
One suggestion is to handle that async response through a callback which is passed as an argument to your authenticate method. Here I'm using Java 8 lambda for that one method interface.
public class MyActivity extends Activity {
public interface AuthCallback {
void onAuthResult(String token);
}
private String authenticate(AuthCallback callback) {
// ...
String responseObjectAccessToken = responseObject.getString("accesstoken");
callback.onAuthResult(responseObjectAccessToken)
// ...
}
public void doSomething(View v) {
authenticate((token) -> {
// do something with token
});
}
// ...
}
As you can see that callback could be stored if you wanted to:
// ...
AuthCallback cb = new AuthCallback() {
#Override
public void onAuthResult(String token) {
// do something in the view/fragment/activity
}
}
// then pass it as argument
or your class could implement this interface and pass itself into the method:
public class MyActivity extends Activity implements AuthCallback {
#Override
public void onAuthResult(String token) {
// do something
}
// ...
public void doSomething(View v) {
authenticate(MyActivity.this); // <-- pass itself
}
}
One important point here is that networking happens on a separate thread, so if you want to have some UI changes after your API responds you could use a helper method runOnUiThread to do changes on main ui thread instead:
authenticate((token) -> {
runOnUiThread(() -> {
// do something with UI here
})
});
I'm trying to build a library that basically wraps our api. Basically, the structure im going for is something like this:
MySDK mySDK = new MySDK("username", "password");
mySDK.getPlaylistInfo("3423", 2323, new CustomCallback<>(){
//on response
//on failure
});
So with vanilla Retrofit, an api call usually looks something like the following:
ApiService api = retrofit.create(ApiService.class);
Call<Response> call = api.getPlaylistInfo()
call.enqueue(new Callback<Response>() {
#Override
public void onResponse(Call<Response> call, Response<Response> response) {
//handle response
}
#Override
public void onFailure(Call<Response> call, Throwable t) {
//handle failure
}
});
Basically, how would I wrap retrofits callback system into my own? Note, the reason for needing to do this is to preprocess the data returned from the api before delivering the final response.
I've written something similar so it might help you getting started, this follows an implementation I'v written for Volley, and re-used when I migrated to Retrofit2 so it resembles it (this SO question).
Create a global object (what you would refer to as MySDK) as a singelton class that handles your requests:
create a singleton class, which you instatiate when you're application comes up:
public class NetworkManager
{
private static final String TAG = "NetworkManager";
private static NetworkManager instance = null;
private static final String prefixURL = "http://some/url/prefix/";
//for Retrofit API
private Retrofit retrofit;
private ServicesApi serviceCaller;
private NetworkManager(Context context)
{
retrofit = new Retrofit.Builder().baseUrl(prefixURL).build();
serviceCaller = retrofit.create(ServicesApi.class);
//other stuf if you need
}
public static synchronized NetworkManager getInstance(Context context)
{
if (null == instance)
instance = new NetworkManager(context);
return instance;
}
//this is so you don't need to pass context each time
public static synchronized NetworkManager getInstance()
{
if (null == instance)
{
throw new IllegalStateException(NetworkManager.class.getSimpleName() +
" is not initialized, call getInstance(...) first");
}
return instance;
}
public void somePostRequestReturningString(Object param1, final SomeCustomListener<String> listener)
{
String url = prefixURL + "this/request/suffix";
Map<String, Object> jsonParams = new HashMap<>();
jsonParams.put("param1", param1);
Call<ResponseBody> response;
RequestBody body;
body = RequestBody.create(okhttp3.MediaType.parse(JSON_UTF), (new JSONObject(jsonParams)).toString());
response = serviceCaller.thePostMethodYouWant("someUrlSufix", body);
response.enqueue(new Callback<ResponseBody>()
{
#Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> rawResponse)
{
try
{
String response = rawResponse.body().string();
// do what you want with it and based on that...
//return it to who called this method
listener.getResult("someResultString");
}
catch (Exception e)
{
e.printStackTrace();
listener.getResult("Error1...");
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable throwable)
{
try
{
// do something else in case of an error
listener.getResult("Error2...");
}
catch (Exception e)
{
throwable.printStackTrace();
listener.getResult("Error3...");
}
}
});
}
public void someGetRequestReturningString(Object param1, final SomeCustomListener<String> listener)
{
// you need it all to be strings, lets say id is an int and name is a string
Call<ResponseBody> response = serviceCaller.theGetMethodYouWant
(String.valueOf(param1.getUserId()), param1.getUserName());
response.enqueue(new Callback<ResponseBody>()
{
#Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> rawResponse)
{
try
{
String response = rawResponse.body().string();
// do what you want with it and based on that...
//return it to who called this method
listener.getResult("someResultString");
}
catch (Exception e)
{
e.printStackTrace();
listener.getResult("Error1...");
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable throwable)
{
try
{
// do something else in case of an error
listener.getResult("Error2...");
}
catch (Exception e)
{
throwable.printStackTrace();
listener.getResult("Error3...");
}
}
});
}
}
This works with your interface (example with POST and GET request, GET could be without params):
public interface BelongServicesApi
{
#POST("rest/of/suffix/{lastpart}") // with dynamic suffix example
Call<ResponseBody> thePostMethodYouWant(#Path("lastpart") String suffix, #Body RequestBody params);
#GET("rest/of/suffix") // with a fixed suffix example
Call<ResponseBody> theGetMethodYouWant(#Query("userid") String userid, #Query("username") String username);
}
when your application comes up:
public class MyApplication extends Application
{
//...
#Override
public void onCreate()
{
super.onCreate();
NetworkManager.getInstance(this);
}
//...
}
a simple listener interface for your callback (seperate file would do good):
public interface SomeCustomListener<T>
{
public void getResult(T object);
}
and finally, from wherever you want, the context is already in there, just call:
public class BlaBla
{
//.....
public void someMethod()
{
//use the POST or GET
NetworkManager.getInstance().somePostRequestReturningString(someObject, new SomeCustomListener<String>()
{
#Override
public void getResult(String result)
{
if (!result.isEmpty())
{
//do what you need with the result...
}
}
});
}
}
you can use any object with the listener, just parse the response string to a corresponding object, depending on what you need to receive and you can call that from everywhere (onClicks, etc.), just remember the objects need to match between methods.
Hope this Helps!
I am new to android programming and I am trying to connect to server with retrofit and get some data. I made a little example just to check if it would return some data. First there is a problem that I don't know if I even wrote the code to do what I want and second I get the errors:
"Error:(64, 52) error: is not abstract and does not override abstract method failure(RetrofitError) in Callback"
and 2 errors " Error:(67, 13) error: method does not override or implement a method from a supertype"
Here is my code
public class MainActivity extends ListActivity{
public static final String ENDPOINT = "http://tinoba.hostzi.com";
List<Jelovnik> jelovnik;
Button gumb;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gumb = (Button)findViewById(R.id.gumb);
}
public void stisni(View view) {
RestAdapter adapter = new RestAdapter.Builder()
.setEndpoint(ENDPOINT)
.build();
JelovnikAPI api = adapter.create(JelovnikAPI.class);
api.getFeed(new Callback<List<Jelovnik>>() {
#Override
public void onResponse(Response<List<Jelovnik>> response, Retrofit retrofit) {
jelovnik = response.body();
gumb.setText(jelovnik.get(0).getIme().toString());
}
#Override
public void onFailure(Throwable throwable) {
}
});
}
}
and my retrofit interface
public interface JelovnikAPI {
#GET("/read.php")
public void getFeed(Callback<List<Jelovnik>> response);
}
The version of Callback you are using is from Retrofit 2 and you are still using Retrofit 1.x. Callback has two methods, failure and success. Your callback should look like
new Callback<List<Jelovnik>>() {
#Override
success(List<Jelovnik> t, Response response) {
}
#Override
public void failure(RetrofitError error) {
}
});
Replace Throwable with RetrofitError:
#Override
public void onFailure(RetrofitError retrofitError) {
}
I'm new to retrofit and i am trying te get a json response to an object called RootObject. The error that i am stuck with is :
"Error:(21, 44) error: incompatible types: NewsController cannot be
converted to Callback>"
Does someone now my mistake here? thanks in regards!
public class NewsController {
public void getNews(){
Retrofit retrofit = new Retrofit.Builder().baseUrl("apilink").addConverterFactory(GsonConverterFactory.create()).build();
GetNewsService service = retrofit.create(GetNewsService.class);
try {
service.GetNewsItems().enqueue(this); //asynchronous
Response<List<RootObject>> response = service.GetNewsItems().execute(); //synchronous
}
catch (IOException e){
}
}
}
class to put the data:
public class RootObject implements Serializable {
public ArrayList<Result> results ;
public int nextId;
public ArrayList<Result> getResults() { return results; }
public int getNextId() { return nextId; }
public String toString() {
return String.format("JEEJ" + nextId);
}
}
Interface:
public interface GetNewsService {
#GET("/Articles")
Call<List<RootObject>> GetNewsItems();
}
First of all,
change your interface to this:
public interface GetNewsService {
#GET("/Articles")
void GetNewsItems(Callback<List<RootObject>> cb);
}
Also change your newsController class.
public class NewsController {
private RestAdapter restAdapter;
static final String API_URL = "[Enter your API base url here]";
public void getNews(){
OkHttpClient mOkHttpClient = new OkHttpClient();
mOkHttpClient.setConnectTimeout(15000,TimeUnit.MILLISECONDS);
mOkHttpClient.setReadTimeout(15000,TimeUnit.MILLISECONDS);
restAdapter = new RestAdapter.Builder().setEndpoint(API_URL).setClient(new OkClient(mOkHttpClient)).setLogLevel(RestAdapter.LogLevel.FULL) .build();
GetNewsService service = restAdapter.create(GetNewsService.class);
Callback<List<RootObject> cb = new Callback<List<RootObject>>() {
#Override
public void success(List<RootObject> rootObjectList, Response response) {
//whatever you want to do with the fetched news items
}
#Override
public void failure(RetrofitError error) {
//whatever you want to do with the error
}
};
service.GetNewsItems(cb);
}
}
You'll need to add the following dependencies in your build.gradle:
compile 'com.squareup.retrofit:retrofit:1.9.0'
compile 'com.google.code.gson:gson:2.3.1'
compile 'com.squareup.okhttp:okhttp:2.4.0'
#megh vidani's answer works, but he had you switch your code from Retrofit 2 to Retrofit 1. Here is how to do it in Retrofit 2. You would need to go back to your original gradle settings, etc. --
public class NewsController {
public void getNews(){
Retrofit retrofit = new Retrofit.Builder().baseUrl("apilink").addConverterFactory(GsonConverterFactory.create()).build();
GetNewsService service = retrofit.create(GetNewsService.class);
service.GetNewsItems().enqueue(new Callback<List<RootObject>>() {
#Override
public void onResponse(Response<List<RootObject>> response) {
// Handle your response
// Note HTTP errors are delivered here, you can check
// response.isSuccess() or response.code() to determine
// HTTP failures
}
#Override
public void onFailure(Throwable t) {
// Network errors
}
});
}
}