App crashes when no internet during Retrofit2 Get request - android

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);

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.

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
}
})

You need to add internet checks before calling your retrofit service because to get some data from server, internet connectivity is mandatory

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;
}
}

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;

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.

Related

Listening to network changes using Connectivity Manager

I am trying to listen to network changes using method registerDefaultNetworkCallback() of conenctivityManager
Using the code below from this answer
val connectivityManager = cotnext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager?.let {
it.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
//take action when network connection is gained
}
override fun onLost(network: Network) {
//take action when network connection is lost
}
})
}
but I have a few questions about this method:
what if the phone is connected to wifi but the wifi is not connected to Internet
In the method documentation I read this which I don't understand, when exactly will the limit will hit? If the callback is called 100 times then an Exception will be thrown? And how to handle this?
To avoid performance issues due to apps leaking callbacks, the system will limit the number of outstanding requests to 100 per app (identified by their UID), shared with all variants of this method, of requestNetwork as well as ConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback. Requesting a network with this method will count toward this limit. If this limit is exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, make sure to unregister the callbacks with unregisterNetworkCallback(ConnectivityManager.NetworkCallback).
what if the phone is connected to wifi but the wifi is not connected
to Internet
The answer, this method will return false
In the method documentation I read this which I don't
understand, when exactly will the limit will hit? If the callback is
called 100 times then an Exception will be thrown? And how to handle
this?
I think it means if you cant register more than 100 callback
At first, add the ConnectivityReceiver class:
class ConnectivityReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (connectivityReceiverListener != null) {
connectivityReceiverListener!!.onNetworkConnectionChanged(
isConnectedOrConnecting(
context
)
)
}
}
private fun isConnectedOrConnecting(context: Context): Boolean {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (cm != null) {
if (Build.VERSION.SDK_INT < 23) {
val ni = cm.activeNetworkInfo
if (ni != null) {
return ni.isConnected && (ni.type == ConnectivityManager.TYPE_WIFI || ni.type == ConnectivityManager.TYPE_MOBILE)
}
} else {
val n = cm.activeNetwork
if (n != null) {
val nc = cm.getNetworkCapabilities(n)
return nc!!.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || nc!!.hasTransport(
NetworkCapabilities.TRANSPORT_WIFI
)
}
}
}
return false
}
interface ConnectivityReceiverListener {
fun onNetworkConnectionChanged(isConnected: Boolean)
}
companion object {
var connectivityReceiverListener: ConnectivityReceiverListener? = null
}
}
Then In your BaseActivity or MainActivity add these lines:
abstract class BaseActivity:AppCompatActivity(),
ConnectivityReceiver.ConnectivityReceiverListener {
var receiver: ConnectivityReceiver? = null
override fun onResume() {
super.onResume()
try {
receiver = ConnectivityReceiver()
registerReceiver(
receiver!!,
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
)
connectivityReceiverListener = this
} catch (ex: Exception) {
//Timber.d("Base ex ${ex.localizedMessage}")
}
}
override fun onPause() {
try {
unregisterReceiver(receiver!!)
receiver = null
} catch (ex: Exception) {
}
super.onPause()
}
override fun onNetworkConnectionChanged(isConnected: Boolean) {
showMessage(isConnected)
}
private fun showMessage(isConnected: Boolean) {
try {
if (!isConnected) {
Log.d("Connection state"," disconnected")
} else {
Log.d("Connection state"," connected")
}
} catch (ex: Exception) {
}
}
}
You should register the receiver in the OnResume method and unregister it in theOnPause method

How can I use work manager in my code - android

This is My Code :
#RequiresApi(Build.VERSION_CODES.M)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.home_activity)
loadProductForTheFirst()
#RequiresApi(Build.VERSION_CODES.M)
private fun hasNetworkAvilable(context: Context): Boolean {
val service = Context.CONNECTIVITY_SERVICE
val manager = context.getSystemService(service) as ConnectivityManager
val network = manager.activeNetwork
return (network != null)
}
#RequiresApi(Build.VERSION_CODES.M)
fun loadProductForTheFirst(){
swipeRefreshMain.isRefreshing = true
viewModel.getalldata().observe(this, Observer {
if (!it.isNullOrEmpty()) {
recycler_main.apply {
layoutManager = GridLayoutManager(this#HomeActivity, 2)
adapter = RecyclerAdapterMain(it, this#HomeActivity)
swipeRefreshMain.isRefreshing = false
}
} else {
if (hasNetworkAvilable(this)) {
viewModel.products.observe(this, Observer {
recycler_main.apply {
layoutManager = GridLayoutManager(this#HomeActivity, 2)
adapter = RecyclerAdapterMain(it, this#HomeActivity)
swipeRefreshMain.isRefreshing = false
}
})
viewModel.setup()
} else {
/// in here if the user not internet for loading the products
/// the alert dialog displays .
AlertDialog.Builder(this)
.setTitle("Internet State")
.setMessage("please turn on your internet connection")
.create()
.show()
/// in here I want a method ( workmanager )
// that as soon as the internet be accessible
/// my product will be updated .
}
}
})
}
well , For the first time that user open my app need the internet to load product from api .
So I just want the method like WorkManager to check if the intenrnet avalibility is accessible .
And after that my method will be load from api .
I did some search but could'nt find any useful example of work with workmanager.
anyone can help me with this . ?
I did this code and work for me .
I put it here if someone looking for this method .
I used work manager to get data from api whenever the network is on .
val constraints = Constriants.builder(this)
.setRequiredNetworkType(NetworkType.Connected)
val workManager : WorkManager = WorkManager.getInstance(this)
val oneRequestWork = OneRequestWorker.build(UploadWorker::class.java)
.setconstrints(constraints)
.build
workmanager.enqueue(oneRequestWork)
the Upload worker class :
class UploadWorker(context : Context , param : WorkerParameters) : Worker(context , param)
private val viewModel: ViewModelRoom by lazy {
ViewModelProvider(
ViewModelStore(),
FactoryRoom(RepositoryCart(DataBaseRoom.invoke(applicationContext)))
)
.get(ViewModelRoom::class.java)
}
override fun dowork() : Result {
return try {
viewModel.setup()
Result.success()
} catch (e: Exception) {
Result.failure()
}

How i can handle network disconnect using RxJava 2

I have some problems, which bound with lose of network connection. How i can handle it in RxJava 2? Thank you very much.
i have this method:
Disposable disposable = api.setStatus(params)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.doOnSubscribe(listener::onPreExecute)
.doFinally(listener::onPostExecute)
.subscribe(serviceRequest -> handleResponse(listener, serviceRequest), listener::onError);
//////////////
#POST("set_status")
Single<OrderResponse> setStatus(#FieldMap Map<String, String> params);
New answer after question was update:
You can handle it in doOnError or your listener::onError:
Disposable disposable = api.setStatus(params)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.doOnError(error->{
handleError(error)
})
.subscribe(serviceRequest -> handleResponse(listener, serviceRequest), listener::onError);
void handleError(Throwable error){
if (error instanceof IOException){
// handle network error
} else {
if(error instanceof SocketTimeoutException){
// handle timeout error
}
}
}
Old answer about subscribing to connection change:
For this purposes you need to catch the network connection change and dispatch it via BehaviorSubject.
Like this:
class NetworkManager(
private val context: Context
) {
private val state: BehaviorSubject<Boolean> = BehaviorSubject.create()
private val receiver = object : BroadcastReceiver() {
override fun onReceive(c: Context?, intent: Intent?) {
state.onNext(isConnected())
}
}
init {
val intentFilter = IntentFilter()
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
context.registerReceiver(receiver, intentFilter)
state.onNext(isConnected())
}
fun subscribe(): Observable<Boolean> {
return state
}
fun isConnected(): Boolean {
val cm = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val netInfo = cm.activeNetworkInfo
return netInfo != null && netInfo.isConnectedOrConnecting
}
}
Create this classs in your Application scope and it will be ok
if you return Single for network calls you can use the onErrorReturn method to catch error and return state to indicate network error.
Handle in onError method as below
void onError(Throwable throwable) {
if(throwable instanceof IOException) {
//Handle network error
}else if(throwable instanceof SocketTimeoutException) {
//Handle Request timeout
}else {
//Show some error like something went wrong
}
}

Force Android to Use Wifi network with no internet

I am building an android app that needs to communicate over a WiFi network that will not have any internet access. The problem is that even when the WiFi is connected android chooses to use cellular/mobile data when no connection internet is present on the wifi network.
I have read many posts on the issue many of which involve rooting the device but that is not possible with a production app (rooting devices is not an option). other solution (like my code bellow) suggest using bindProcessToNetwork() which works perfectly on my Sony Z2 but not on other devices I have tested on (all running 6.0.1)
private void bindToNetwork() {
final ConnectivityManager connectivityManager = (ConnectivityManager) mActivity.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkRequest.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder = new NetworkRequest.Builder();
//set the transport type do WIFI
builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
connectivityManager.requestNetwork(builder.build(), new ConnectivityManager.NetworkCallback() {
#Override
public void onAvailable(Network network) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
connectivityManager.bindProcessToNetwork(null);
if (barCodeData.getSsid().contains("ap-name")) {
connectivityManager.bindProcessToNetwork(network);
}
} else {
//This method was deprecated in API level 23
ConnectivityManager.setProcessDefaultNetwork(null);
if (barCodeData.getSsid().contains("ap-name")) {
ConnectivityManager.setProcessDefaultNetwork(network);
}
}
connectivityManager.unregisterNetworkCallback(this);
}
});
}
}
You can solve this by setting captive_portal_detection_enabled to 0 (false).
What's actually happening is that by default, everytime you connect to a wifi, the FW will test against a server (typically google) to see if it's a captive wifi (needs login). So if your wifi is not connected to google, this check will fail. After that, the device knows that wifi has no internet connection and simply will not autoconnect to it.
Setting this setting to 0 will avoid this check.
Programatically:
Settings.Global.putInt(getContentResolver(), Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 0);
Edit: You may need to use the string "captive_portal_detection_enabled" directly, instead of the constant that's not visible depending on Android version.
you'd need to disable mobile data in the Settings (not certain, if this can be done programmatically, which might be a possible option) - or take out the USIM;
else the common behavior is, that it will always fall back to the best available connection (while a connection with internet gateway might be preferred, because it is used by most application).
also see this answer.
Solution on Kotlin
class ConnectWithoutInternetTest constructor(
private val mContext: Context,
private val connectivityManager: ConnectivityManager,
private val wifiManager: WifiManager
) {
private val mWifiBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
WifiManager.NETWORK_STATE_CHANGED_ACTION -> {
val info = intent.getParcelableExtra<NetworkInfo>(WifiManager.EXTRA_NETWORK_INFO)
val isConnected = info.isConnected
val ssid: String? = normalizeAndroidWifiSsid(wifiManager.connectionInfo?.ssid)
if (isConnected) {
val builder = NetworkRequest.Builder()
builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
connectivityManager.registerNetworkCallback(
builder.build(),
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
val networkInfo = connectivityManager.getNetworkInfo(network)
val networkSsid = networkInfo.extraInfo
if (networkSsid == ssid) {
connectivityManager.unregisterNetworkCallback(this)
}
}
})
}
}
}
}
}
private fun init() {
val intentFilter = IntentFilter()
intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION)
mContext.registerReceiver(mWifiBroadcastReceiver, intentFilter)
}
private fun destroy() {
mContext.unregisterReceiver(mWifiBroadcastReceiver)
}
private fun normalizeAndroidWifiSsid(ssid: String?): String? {
return ssid?.replace("\"", "") ?: ssid
}
fun connectToWifi(ssidParam: String, password: String?) {
init()
val ssid = "\"$ssidParam\""
val config = wifiManager.configuredNetworks.find { it.SSID == ssid }
val netId = if (config != null) {
config.networkId
} else {
val wifiConfig = WifiConfiguration()
wifiConfig.SSID = ssid
password?.let { wifiConfig.preSharedKey = "\"$password\"" }
wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
wifiManager.addNetwork(wifiConfig)
}
wifiManager.disconnect()
val successful = wifiManager.enableNetwork(netId, true)
}
You're in the right path, the solution is indeed with ConnectivityManager.bindProcessToNetwork(network). This information was posted on the Android Developers Blog in this article: Connecting your App to a Wi-Fi Device.
Looking into your code this barCodeData.getSsid() doesn't look that is getting the SSID of the currently connected wifi network. If that's the case your code will never successfully bind to the network.
Try replace your if statement
if (barCodeData.getSsid().contains("ap-name"))
With
if (getNetworkSsid(context).equals("ap-name"))
Helper method in kotlin to retrieve the SSID of the connected wifi network
private fun getNetworkSsid(context: Context?): String {
// WiFiManager must use application context (not activity context) otherwise a memory leak can occur
val mWifiManager = context?.applicationContext?.getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiInfo: WifiInfo? = mWifiManager.connectionInfo
if (wifiInfo?.supplicantState == SupplicantState.COMPLETED) {
return wifiInfo.ssid.removeSurrounding("\"")
}
return ""
}
If still doesn't work please follow my complete solution where I used the same method but with some extra checks. I tested it in the Android versions 5.1.1, 6.0, 6.0.1, 7.1.1 and 8.1.0.
You're on the right path, you only need to tweak what you have a bit. One of the main issues I see is that you unregister the network callback on onAvailable() which is not a good idea as networks tend to switch between available/unavailable from time-to-time which would cause issues on your device.
The other thing to consider is requesting a network that's Wi-Fi and clearing the other network capabilities as they may cause you issues depending on your network's setup.
Here is another version of how to do that which is hopefully a bit simpler.
final NetworkRequest requestForWifi =
new NetworkRequest.Builder()
.clearCapabilities() // We only care about Wi-Fi
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build();
final ConnectivityManager connectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkCallback networkCallbackWifi = new NetworkCallback() {
#Override
void onAvailable(Network network) {
// Once this callback triggers, a Wi-Fi network is available
WifiInfo wifiInfo = connectivity.getNetworkCapabilities(network).TransportInfo;
string ssid = wifiInfo.SSID.Trim(new char[] {'"', '\"' });
if (!ssid.contains("ap-name")) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
connectivityManager.bindProcessToNetwork(network);
} else {
ConnectivityManager.setProcessDefaultNetwork(network);
}
}
};
// Last thing is to actually request a network.
connectivityManager.requestNetwork(requestForWifi, networkCallbackWifi);
You can check if wifi is connected then proceed else show a dialog to user asking him to connect to a wifi network
Since the method NetworkInfo.isConnected() is now deprecated in API-23, here is a method which detects if the Wi-Fi adapter is on and also connected to an access point using WifiManager instead:
private boolean checkWifiOnAndConnected() {
WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE);
if (wifiMgr.isWifiEnabled()) { // Wi-Fi adapter is ON
WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
if( wifiInfo.getNetworkId() == -1 ){
return false; // Not connected to an access point
}
return true; // Connected to an access point
}
else {
return false; // Wi-Fi adapter is OFF
}
}

Send request over WiFi (without connection) even if Mobile data is ON (with connection) on Android M

I have to send UDP packets to a WiFi module (provided with own AP) with no internet connection but when I connect the mobile with the AP, Android redirects my packets on the mobile data interface because it has got internet connection.
I've used the code below to do my job but it seems not working on Android M.
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setWifiInterfaceAsDefault() {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkRequest.Builder builder = new NetworkRequest.Builder();
NetworkRequest networkRequest= builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build();
connectivityManager.requestNetwork(networkRequest, new ConnectivityManager.NetworkCallback());
}
I've also added
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
on my AndroidManifest.xml and I ensured myself that Settings.System.canWrite(this) returns true but still nothing.
Thanks in advance.
Stanislav's answer is correct but incomplete because only works in Lollipop.
I've wrote a complete solution for Lollipop and Marshmallow onwards for you to route all network requests through WiFi when connected to a specific network of your choice.
Kotlin
In your Activity,
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class RoutingActivity : Activity() {
private var mConnectivityManager: ConnectivityManager? = null
private var mNetworkCallback: ConnectivityManager.NetworkCallback? = null
//...
override fun onCreate(savedInstanceState: Bundle?) {
//...
routeNetworkRequestsThroughWifi("Access-Point-SSID-You-Want-To-Route-Your-Requests")
}
Route future network requests from application through WiFi (even if given WiFi network is without internet and mobile data has internet connection)
/**
* This method sets a network callback that is listening for network changes and once is
* connected to the desired WiFi network with the given SSID it will bind to that network.
*
* Note: requires android.permission.INTERNET and android.permission.CHANGE_NETWORK_STATE in
* the manifest.
*
* #param ssid The name of the WiFi network you want to route your requests
*/
private fun routeNetworkRequestsThroughWifi(ssid: String) {
mConnectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
// ensure prior network callback is invalidated
unregisterNetworkCallback(mNetworkCallback)
// new NetworkRequest with WiFi transport type
val request = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build()
// network callback to listen for network changes
mNetworkCallback = object : ConnectivityManager.NetworkCallback() {
// on new network ready to use
override fun onAvailable(network: Network) {
if (getNetworkSsid(this#RoutingActivity).equals(ssid, ignoreCase = false)) {
releaseNetworkRoute()
createNetworkRoute(network)
} else {
releaseNetworkRoute()
}
}
}
mConnectivityManager?.requestNetwork(request, mNetworkCallback)
}
Unregister network callback
private fun unregisterNetworkCallback(networkCallback: ConnectivityManager.NetworkCallback?) {
if (networkCallback != null) {
try {
mConnectivityManager?.unregisterNetworkCallback(networkCallback)
} catch (ignore: Exception) {
} finally {
mNetworkCallback = null
}
}
}
Create network route
private fun createNetworkRoute(network: Network): Boolean? {
var processBoundToNetwork: Boolean? = false
when {
// 23 = Marshmallow
Build.VERSION.SDK_INT >= 23 -> {
processBoundToNetwork = mConnectivityManager?.bindProcessToNetwork(network)
}
// 21..22 = Lollipop
Build.VERSION.SDK_INT in 21..22 -> {
processBoundToNetwork = ConnectivityManager.setProcessDefaultNetwork(network)
}
}
return processBoundToNetwork
}
 Release network route
private fun releaseNetworkRoute(): Boolean? {
var processBoundToNetwork: Boolean? = false
when {
// 23 = Marshmallow
Build.VERSION.SDK_INT >= 23 -> {
processBoundToNetwork = mConnectivityManager?.bindProcessToNetwork(null)
}
// 21..22 = Lollipop
Build.VERSION.SDK_INT in 21..22 -> {
processBoundToNetwork = ConnectivityManager.setProcessDefaultNetwork(null)
}
}
return processBoundToNetwork
}
Helper
private fun getNetworkSsid(context: Context?): String {
// WiFiManager must use application context (not activity context) otherwise a memory leak can occur
val mWifiManager = context?.applicationContext?.getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiInfo: WifiInfo? = mWifiManager.connectionInfo
if (wifiInfo?.supplicantState == SupplicantState.COMPLETED) {
return wifiInfo.ssid.removeSurrounding("\"")
}
return ""
}
Bind the network using ConnectivityManager.setProcessDefaultNetwork() prevents roaming and allows for full TCP access. Thus, within the onAvailable() callback you could bind the application process to that network rather than opening a connection to a particular URL.
ConnectivityManager connection_manager =
(ConnectivityManager) activity.getApplication().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkRequest.Builder request = new NetworkRequest.Builder();
request.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
connection_manager.registerNetworkCallback(request.build(), new NetworkCallback() {
#Override
public void onAvailable(Network network) {
ConnectivityManager.setProcessDefaultNetwork(network);
}
}
Original answer

Categories

Resources