'App crashes when no internet during Retrofit2 Get request
My application seems to keep crashing giving me an E/AndroidRuntime: FATAL EXCEPTION: main error when i try to make a Get request to a server and there is no internet. I expected the app to run but no data be displayed.
Log.i("getStoreData()" , "Inside the coroutine before getData")
this is the last log that I have put myself gets printed before the app crashes.
private fun getStoreData() {
Log.i("getStoreData()", " inside getStoreData")
val job = coroutineScope.launch {
Log.i("getStoreData()" , "Inside the coroutine before getData")
var data = StoreAPI.retrofitService.getData()
Log.i("getStoreData()" , "Inside the coroutine after getData")
try {
var storeData = data.stores
_status.value = "Success: ${storeData.size} Stores received"
if(storeData.size > 0){
_stores.value = storeData
}
} catch (t: Throwable) {
Log.i("Retrofit catch block", _status.value)
_status.value = "Failure: " + t.message
t.printStackTrace()
}
}
}
StoreAPIService.kt
private const val URL = "http://sandbox.bottlerocketapps.com/BR_Android_CodingExam_2015_Server/"
private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(URL)
.build()
interface StoreAPIService{
//Initially was using Jake Wharton's library for retrofit2 kotlin coroutines support but it has been deprecated since the support
// addition of the suspend keyword in retrofit 2.6.0
//Suspend does all the task of coroutines for us by just adding it before the function declaration
@GET("stores.json")
suspend fun getData():
Data //return Data object because Data has access to the Store JSON Object/Array
}
object StoreAPI{
val retrofitService: StoreAPIService by lazy {
retrofit.create(StoreAPIService::class.java)
}
}
Any idea why?
EDIT:
I cannot use these network connectivity functions because I my fragment is not connected to any activity and the fragment is connected to a viewModel. Therefore this line of code doesn't work as there is no context to bound it to. If you have a work around for this that would be great too.
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
Solution 1:[1]
NB: before making any Network call or sending any requesting you must ensure that the device is connected to internet. I entice you to write a simple function to check if you're connected, if you're connected then you can send the request or make a network call.
Solution 2:[2]
Try using this Create Class For NetworkConnectionDetection
Manifest
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
NetworkConnection Class
class NetworkConnection(val context: Context) : LiveData<Boolean>() {
var connectionManger: ConnectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
lateinit var netwrokCallback: ConnectivityManager.NetworkCallback
override fun onActive() {
super.onActive()
updateConnection()
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> {
connectionManger.registerDefaultNetworkCallback(NetworkConnectioncallback())
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> {
lollipopNetworkRequest()
}
else -> {
context.registerReceiver(
networkReciever(),
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
)
}
}
}
/*override fun onInactive() {
super.onInactive()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
connectionManger.unregisterNetworkCallback(NetworkConnectioncallback())
} else {
context.unregisterReceiver(networkReciever())
}
}*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
fun lollipopNetworkRequest() {
val requestBuilder = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
connectionManger.registerNetworkCallback(
requestBuilder.build(),
NetworkConnectioncallback()
)
}
fun NetworkConnectioncallback(): ConnectivityManager.NetworkCallback {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
netwrokCallback = object : ConnectivityManager.NetworkCallback() {
override fun onLost(network: Network) {
super.onLost(network)
postValue(false)
}
override fun onAvailable(network: Network) {
super.onAvailable(network)
postValue(true)
}
}
return netwrokCallback
} else {
throw IllegalAccessError("Error!")
}
}
fun networkReciever() = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
updateConnection()
}
}
fun updateConnection() {
val activeNetwork: NetworkInfo? = connectionManger.activeNetworkInfo
postValue((activeNetwork?.isConnected == true))
}
}
Now Inside your Activity/Fragment Check the connection either it is connected or not. Here is how it will be achieved
val networkConnection = NetworkConnection(requireContext())
networkConnection.observe(viewLifecycleOwner, { isConnected ->
if (isConnected) {
// Do what ever you want to do
} else {
// Show No internet connection message
}
})
Solution 3:[3]
You need to add internet checks before calling your retrofit service because to get some data from server, internet connectivity is mandatory
Solution 4:[4]
This method checks whether mobile is connected to internet and returns true
if connected:
private boolean isNetworkConnected() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
return cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected();
}
in manifest,
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Edit: This method actually checks if device is connected to internet(There is
a possibility it's connected to a network but not to internet).
public boolean isInternetAvailable() {
try {
InetAddress ipAddr = InetAddress.getByName("google.com");
//You can replace it with your name
return !ipAddr.equals("");
}catch (Exception e) {
return false;
}
}
Solution 5:[5]
This will tell you if you're connected to a network:
boolean connected = false;
ConnectivityManager connectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
if(connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState() == NetworkInfo.State.CONNECTED ||
connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState() == NetworkInfo.State.CONNECTED) {
//we are connected to a network
connected = true;
}
else
connected = false;
Solution 6:[6]
I also followed that tutorial, but I don't remember it having an offline mode. That is an option that you have to integrate on yourself.
When you create the viewModel, because it has an init block, it makes the call to the API and if you don't have an Internet connection, it crashes.
So you should write the init viewModel some code that checks whether you have an Internet connection or not. Or in the method that makes the API call to get the data.
In the next lesson from that tutorial, "Behind the scenes", they talk about offline mode.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Emmanuel Ametepee |
| Solution 2 | Hashaam Khurshid |
| Solution 3 | Kartika Vij |
| Solution 4 | Jakir Hossain |
| Solution 5 | Farhan Fahim |
| Solution 6 | PhillauSofia |

