It’s a common task for a Firemonkey developer to check Internet connectivity. It might be useful to notify a user that he is going to download a huge amount of data using his mobile connection, or just indicate Online/Offline mode on the UI.
This functionality is missing from RAD Studio classes, so lots of developers out there have made solutions for their needs, although most of them only solve problems of their developer and may not fulfill your needs.
In order to fill that gap I made a solution which follows these guidelines:
- Android and iOS support
- ability to retrieve current Internet connectivity state – disconnected, connected to WiFi, connected to mobile data
- Internet connectivity state listener which fires every time when connectivity changes
- a cross-platform interface with encapsulated platform-specific solutions
firemonkey-network-state
The completed solution called “firemonkey-network-state” and available on GitHub – https://github.com/Code-Partners/firemonkey-network-state.
How to integrate
- clone or download the repo and add files from
Source
folder to your project. - For Android build target, enable
ACCESS_NETWORK_STATE
permission - For iOS Delphi project, put
libReachability.a
andlibReachability64.a
into the root directory. For iOS C++ project, addlibReachability64.a
into project (“Project” -> “Add to project” in IDE). These files can be located in “Samples” directory of the repo. Note, that in C++ project you can uselibReachability64.a
both for x32 and x64 builds - You may need to add “SystemConfiguration” framework for the iOS target in RAD Studio SDK Manager. For instructions of how to do it, read this article
I used materials from Dave Nottage as a baseline – http://delphiworlds.com/about-dave-nottage/
How to use
The only required step is to initialize an instance of TNetworkState
class using Factory
class method:
Factory(AOwner: TComponent; AOnChange: TNetworkStateChangeEvent): TNetworkState;
The second parameter is network stage event handler, if you don’t need it, pass nil
.
There is no limitation to the amount of TNetworkState
instances, different parts of the program may safely allocate instances if necessary.
Then, access read-only TNetworkState.CurrentValue
property to retreive current network state. Return value type is TNetworkStateValue
which is an enumeration of defined network states:
TNetworkStateValue = ( nsUnknown = 0, nsConnectedWifi = 1, nsConnectedMobileData = 2, nsDisconnected = 3 );
If you passed an event handler to Factory
method, it’ll get called every time when connectivity changes. Note that event handler gets called in the context of the main thread, so you can work with UI inside of with without any extra synchronization. Update state passed as an event handler parameter:
procedure TSampleForm.DoOnChange(Sender: TObject; Value: TNetworkStateValue); begin // react to network state change end;
Git repository contains sample Delphi and C++ project iOS and Android build targets.
That’s it. If you want to know about limitations and implementation details, please see below.
Limitations
- In C++,
libReachability64.a
file needs to be added to the project. If you have one project both for iOS and Android, you’ll need to exclude.a
file from the build before building Android and enable back before building iOS. Please note that if you’ll forget to enable it back for the iOS target, the project will compile fine but an exception will happen on allocatingTNetworkStateValue
instance - The library does not work in iOS simulator. The reason behind this is that iOS implementation of
TNetworkStateValue
uses a static Objective-C library (libReachability64.a
), and Delphi Linker ignores static libraries while building iOSSimulator target. For convenience, I instantiate dummyTNetworkStateValue
on iOSSimulator platform, so the project will compile and run fine, butCurrentValue
will always equalnsUnknown
and “on change” event will never get called
Android implementation details
Like in Dave’s solution, TNetworkState
retrieves current network state using ConnectivityManager class from Android SDK
Although, this class is not capable to detect when connectivity state changes. Android uses another mechanism to report it – Intent Messages. Every time when connectivity changes, Android broadcasts a specific Intent message to all running apps. In order to receive it, we need an instance of BroadcastReceiver
First, we need to instantiate receiver and subscribe to specific messages, for this task we need only android.net.conn.CONNECTIVITY_CHANGE
. onReceive
callback fires for every inbound message. Note that the callback fires in the context of the Java thread.
BroadcastReceiver
can serve many purposes, such as listening to other system-wide events or inter-process communication.
iOS implementation details
Again, Dave’s solution gets the job done with Internet connectivity retrieving. Unfortunately, there is no convenient API in iOS SDK for managing internet connectivity, so a lot of iOS devs use Reachability class from developer.apple.com
libReachability.a
contains this class in a static library which then linked into Firemonkey app binaries.
Then, we are able to create headers for Reachability
class and instantiate it from Delphi or C++.
Reachability
class does not report about connectivity changes by itself. If we look into its sources, connectivity events get posted to NSNotificationCenter
class from iOS SDK.
In order to subscribe to this event we need to define delegate interface:
INetworkChangeDelegate = interface(IObjectiveC) ['{BC4EABBE-F21F-4592-93B0-0C40415E4A91}'] procedure handleNetworkChange(notice: NSNotification); cdecl; end;
Then, implement this interface with Delphi class derived from TOCLocal
base class:
TNetworkChangeDelegate = class (TOCLocal, INetworkChangeDelegate) public // (void) handleNetworkChange:(NSNotification *)notice procedure handleNetworkChange(notice: NSNotification); cdecl; end;
With this being done, we can add observer to connectivity change event:
TNSNotificationCenter.Wrap(TNSNotificationCenter.OCClass.defaultCenter).addObserver( FDelegate.GetObjectID, sel_getUid('handleNetworkChange:'), StringToID('kReachabilityChangedNotification'), nil );
where FDelegate.GetObjectID
is a reference to Objective-C delegate object, sel_getUid('handleNetworkChange:')
is an Objective-C method selector and 'kReachabilityChangedNotification'
is a name of the notification we want to observe.
Note that we need to hold a reference to FDelegate
object, otherwise it will get destroyed by auto reference counting mechanism.
After addObserver
was called, TNetworkChangeDelegate.handleNetworkChange
will get called for every connectivity change.
I have been using your ‘firemonkey-network-state’ code successfully until today. Today, under Delphi Tokyo 10.2.3, I updated my iOS SDK to version 11.3. Since, I did that I am no longer able to use your code.
Every time I try to compile your ‘Sample’ project, I get the following error:
[DCC Error] E2597 ld: warning: unknown dwarf DW_FORM_strp (offset=0xFFFFCF2C) is too big in libReachability.a(Reachability.o)
Would you happen to know how to solve this issue?
Hello.
Linking of static Objective-C libraries is not very smooth, so sometimes it might break when development environment changes.
Please validate if both debug and release builds fail to compile, sometimes release builds fine while debug does not.
Also, these static libraries were generated 3 years ago:
http://delphiworlds.com/2013/11/checking-for-an-internet-connection-on-mobile-devices-with-delphi-xe5/
Maybe, it would be helpful to recompile static Objective-C libraries for in the most recent Xcode+iOS SDK environment, but sources for Xcode project to build that libs were not published, so it’d be necessary to make them from scratch.
I’m sorry that I can’t give you a more specific answer at the moment. If you’ll be able to resolve that issue or have ideas for a workaround, please share your results.
Cheers.