i want to send localnotification to my IOS app. App is checking time from my database when that time match to my mobile time it gives notification, but the problem that local notification works fine when my app is in foreground, when i close app local notification doesnt work any help will be appreciated
i have tried many things but couldnt succeed
here is my code i am running on App.cs
private async void CheckConnection()
{
var httpClient = new HttpClient();
var json = await httpClient.GetStringAsync("http://192.168.10.55:56556/api/ShowAll");
TimeSpan time = TimeSpan.FromSeconds(1);
DateTime dateTime = DateTime.Today.Add(time);
var admtPatients = Newtonsoft.Json.JsonConvert.DeserializeObject<List<ContactDetail>>(json);
ObservableCollection<ContactDetail> trends = new ObservableCollection<ContactDetail>(admtPatients);
foreach (var item in trends)
{
if (item.TimeSchedule == DateTime.Now.ToString("hh:mm:ss"))
{
CrossLocalNotifications.Current.Show("title", "body");
}
}
return;
}
protected override void OnStart()
{
var seconds = TimeSpan.FromSeconds(1);
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
CheckConnection();
return true;
});
// Handle when your app starts
}
protected override void OnSleep()
{
base.OnSleep();
var seconds = TimeSpan.FromSeconds(1);
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
CheckConnection();
return true;
});
}
protected override void OnResume()
{
base.OnSleep();
var seconds = TimeSpan.FromSeconds(1);
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
CheckConnection();
return true;
});
}
When the user is not actively using your app, the system moves it to the background state. For many apps, the background state is just a brief stop on the way to the app being suspended. Suspending apps is a way of improving battery life it also allows the system to devote important system resources to the new foreground app that has drawn the user’s attention.
So ,if you want to start a long-running task when your app transitions to the background.You should request to start a background task includes an expiration handler just in case the task takes too long in your iOS project .
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task,you can use messageingcenter to told forms send notification.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
Related
I am using the following code for calling a function every 10 seconds.
var startTimeSpan = TimeSpan.Zero;
var periodTimeSpan = TimeSpan.FromSeconds(10);
var timer = new System.Threading.Timer((e) =>
{
MyFunction();
}, null, startTimeSpan, periodTimeSpan);
MyFunction() will execute every 10 seconds when the app is on the open state. When the app is in the background, that function is not invoking.
So how can I invoke a function when the app is in the background? Are there any NuGet packages for this or we need to do this using the dependency service?
UPDATE
When I running your demo I am getting the below exception:
I have integrated the codes on my sample. The code execution coming on Start() in the StartServiceDroid. But not hitting the OnStartCommand() function. Here is my sample, could you please have a look? I need to run the MyFunction() every x seconds in the background or foreground mode.
Update 10-07-2020
#Leon Lu - MSFT I found a new solution here. :)
var second = TimeSpan.FromSeconds(10);
Device.StartTimer(second, () => {
Debug.WriteLine("Hiiiii");
return true;
});
I create a sample application with this code and it is working fine on foreground and background mode. Every 10 seconds, the Hiii message is printing on the output box even the app is running on the background.
This is included with Xamarin Forms, so no need for any platform-specific logic.
Is there any problem with this approach?
Update 15/07/2020
Today I have tested it on a real device. :)
Case 1: Run the app on Visual Studio, Service is invoking in foreground mode. Move the app to the background (Still, the app is running in VS), the background service is invoking when the app is on the background every 10 seconds.
Case 2: Open the app installed app normally, (not running it in VS), service is invoking in foreground mode, Move the app to the background, the background service is not invoking even after 10minutes.
I am totally confused now, background service is invoking when the app is running on VS and not invoking when open the installed app normally. For the second case, I have waited for more than 10 minutes, but the service that I added is not invoking. Both cases are done on debug mode.
Is it the only behavior of xamarin forms platform? If we do it in the native ios platform, is it possible to trigger the background service on every x seconds? How skype is doing background service?
Tested device model: iPhone 7
Software version: 13.5.1
In Android, you can use foreground service to keep your MyFunction() always running in background. If you used it in xamarin forms. you can use dependence service to invoke the foreground service, then execute your Timer with MyFunction in foreground service.
Here is simple code about use a DependentService to open a foreground service.
[Service]
public class DependentService : Service, IService
{
public void Start()
{
var intent = new Intent(Android.App.Application.Context,
typeof(DependentService));
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
Android.App.Application.Context.StartForegroundService(intent);
}
else
{
Android.App.Application.Context.StartService(intent);
}
}
public override IBinder OnBind(Intent intent)
{
return null;
}
public const int SERVICE_RUNNING_NOTIFICATION_ID = 10000;
public override StartCommandResult OnStartCommand(Intent intent,StartCommandFlags flags, int startId)
{
// From shared code or in your PCL
CreateNotificationChannel();
string messageBody = "service starting";
var notification = new Notification.Builder(this, "10111")
.SetContentTitle("Foreground")
.SetContentText(messageBody)
.SetSmallIcon(Resource.Drawable.main)
.SetOngoing(true)
.Build();
StartForeground(SERVICE_RUNNING_NOTIFICATION_ID, notification);
//=======you can do you always running work here.=====
var startTimeSpan = TimeSpan.Zero;
var periodTimeSpan = TimeSpan.FromSeconds(10);
var timer = new System.Threading.Timer((e) =>
{
MyFunction();
}, null, startTimeSpan, periodTimeSpan);
return StartCommandResult.Sticky;
}
void CreateNotificationChannel()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
// Notification channels are new in API 26 (and not a part of the
// support library). There is no need to create a notification
// channel on older versions of Android.
return;
}
var channelName = Resources.GetString(Resource.String.channel_name);
var channelDescription = GetString(Resource.String.channel_description);
var channel = new NotificationChannel("10111", channelName, NotificationImportance.Default)
{
Description = channelDescription
};
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
notificationManager.CreateNotificationChannel(channel);
}
}
Here is my demo about how to use foreground service in xamarin form.
https://github.com/851265601/ForeGroundService
In iOS, it cannot be achieved that always running your application in the background, because iOS, just allow normal applications(:Audio, VoIP,External Accessories and Bluetooth Newsstand, Location, Fetch (iOS 7+), Remote Notifications (iOS 7+) application could be allowed allways running in background, you can see this thread) running in background with in 10 minutes. You can refer to this article.
https://learn.microsoft.com/en-us/xamarin/ios/app-fundamentals/backgrounding/ios-backgrounding-techniques/ios-backgrounding-with-tasks
So i'm testing with Blazor and gRPC and my dificulty at the moment is on how to pass the content of a variable that is on a class, specifically the gRPC GreeterService Class to the Blazor page when new information arrives. Notice that my aplication is a client and a server, and i make an initial comunication for the server and then the server starts to send to the client data(numbers) in unary mode, every time it has new data to send. I have all this working, but now i'm left it that final implementation.
This is my Blazor page
#page "/greeter"
#inject GrpcService1.GreeterService GreeterService1
#using BlazorApp1.Data
<h1>Grpc Connection</h1>
<input type="text" #bind="#myID" />
<button #onclick="#SayHello">SayHello</button>
<p>#Greetmsg</p>
<p></p>
#code {
string Name;
string Greetmsg;
async Task SayHello()
{
this.Greetmsg = await this.GreeterService1.SayHello(this.myID);
}
}
The method that later receives the communication from the server if the hello is accepted there is something like this:
public override async Task<RequestResponse> GiveNumbers(BalconyFullUpdate request, ServerCallContext context)
{
RequestResponse resp = new RequestResponse { RequestAccepted = false };
if (request.Token == publicAuthToken)
{
number = request.Number;
resp = true;
}
return await Task.FromResult(resp);
}
Every time that a new number arrives i want to show it in the UI.
Another way i could do this was, within a while condition, i could do a call to the server requesting a new number just like the SayHello request, that simply awaits for a server response, that only will come when he has a new number to send. When it comes the UI is updated. I'm just reluctant to do it this way because i'm afraid that for some reason the client request is forgotten and the client just sit's there waiting for a response that will never come. I know that i could implement a timeout on the client side to handle that, and on the server maybe i could pause the response, with a thread pause or something like that, and when the method that generates the new number has a new number, it could unpause the response to the client(no clue on how to do that). This last solution looks to me much more difficult to do than the first one.
What are your thoughts about it? And solutions..
##################### UPDATE ##########################
Now i'm trying to use a singleton, grab its instance in the Blazor page, and subcribe to a inner event of his.
This is the singleton:
public class ThreadSafeSingletonString
{
private static ThreadSafeSingletonString _instance;
private static readonly object _padlock = new object();
private ThreadSafeSingletonString()
{
}
public static ThreadSafeSingletonString Instance
{
get
{
if (_instance == null)
{
lock(_padlock)
{
if (_instance == null)
{
_instance = new ThreadSafeSingletonString();
_instance.number="";
}
}
}
return _instance;
}
set
{
_instance.number= value.number;
_instance.NotifyDataChanged();
}
}
public int number{ get; set; }
public event Action OnChange;
private void NotifyDataChanged() => OnChange?.Invoke();
And in Blazor page in code section i have:
protected override void OnInitialized()
{
threadSafeSingleton.OnChange += updateNumber();
}
public System.Action updateNumber()
{
this.fromrefresh = threadSafeSingleton.number + " que vem.";
Console.WriteLine("Passou pelo UpdateNumber");
this.StateHasChanged();
return StateHasChanged;
}
Unfortunatly the updatenumber function never gets executed...
To force a refresh of the ui you can call the StateHasChanged() method on your component:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.componentbase.statehaschanged?view=aspnetcore-3.1
Notifies the component that its state has changed. When applicable, this will cause the component to be re-rendered.
Hope this helps
Simple Request
After fully understanding that your problem is just to Update the Page not to get unsyncronous messages from the server with a bi directional connection. So jou just have to change your page like (please not there is no need to change the files generated by gRPC, I called it Number.proto so my service is named NumberService):
async Task SayHello()
{
//Request via gRPC
var channel = new Channel(Host + ":" + Port, ChannelCredentials.Insecure);
var client = new this.NumberService.NumberServiceClient(channel);
var request = new Number{
identification = "ABC"
};
var result = await client.SendNumber(request).RequestAccepted;
await channel.ShutdownAsync();
//Update page
this.Greetmsg = result;
InvokeAsync(StateHasChanged);//Required to refresh page
}
Bi Directional
For making a continious bi directional connection you need to change the proto file to use streams like:
service ChatService {
rpc chat(stream ChatMessage) returns (stream ChatMessageFromServer);
}
This Chant sample is from the https://github.com/meteatamel/grpc-samples-dotnet
The main challenge on this is do divide the task waiting for the gRPC server from the client. I found out that BackgroundService is good for this. So create a Service inherited from BackgroundService where place the while loop waiting for the server in the ExecuteAsyncmethod. Also define a Action callback to update the page (alternative you can use an event)
public class MyChatService : BackgroundService
{
Random _random = new Random();
public Action<int> Callback { get; set; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Replace next lines with the code request and wait for server...
using (_call = _chatService.chat())
{
// Read messages from the response stream
while (await _call.ResponseStream.MoveNext(CancellationToken.None))
{
var serverMessage = _call.ResponseStream.Current;
var otherClientMessage = serverMessage.Message;
var displayMessage = string.Format("{0}:{1}{2}", otherClientMessage.From, otherClientMessage.Message, Environment.NewLine);
if (Callback != null) Callback(displayMessage);
}
// Format and display the message
}
}
}
}
On the page init and the BackgroundService and set the callback:
#page "/greeter"
#using System.Threading
<p>Current Number: #currentNumber</p>
#code {
int currentNumber = 0;
MyChatService myChatService;
protected override async Task OnInitializedAsync()
{
myChatService = new MyChatService();
myChatService.Callback = i =>
{
currentNumber = i;
InvokeAsync(StateHasChanged);
};
await myChatService.StartAsync(new CancellationToken());
}
}
More information on BackgroundService in .net core can be found here: https://gunnarpeipman.com/dotnet-core-worker-service/
I am trying to develop a xamarin forms app in which user can make call
(Navigate to dialer) from taping on number showed on app.In android I accomplished this through dependency service.But in ios I am stuck.I heard about callkit.I saw the documentation of it in https://learn.microsoft.com/en-us/xamarin/ios/platform/callkit?tabs=windows. But how can I actually implement on this in my App? I added all the classes in that document to my app.But how I can make the call from xamal.cs to the ios specified code? By using Dependency service?
Edit: I know how to navigate app to dialer or phone app. Why I am using callkit is I want to get the call duartion.
I created an Instance
public interface IosCallerDialer
{
void StartCall();
}
Implementation on ios
class IosCallDial: IosCallerDialer
{
private CXCallController CallController = new CXCallController();
private void SendTransactionRequest(CXTransaction transaction)
{
// Send request to call controller
CallController.RequestTransaction(transaction, (error) => {
// Was there an error?
if (error == null)
{
// No, report success
Console.WriteLine("Transaction request sent successfully.");
}
else
{
// Yes, report error
Console.WriteLine("Error requesting transaction: {0}", error);
}
});
}
public void StartCall()
{
// Build call action
string contact = "8547085532";
var handle = new CXHandle(CXHandleType.Generic, contact);
var startCallAction = new CXStartCallAction(new NSUuid(), handle);
// Create transaction
var transaction = new CXTransaction(startCallAction);
// Inform system of call request
SendTransactionRequest(transaction);
}
}
My xaml.cs
async void btnCall_Clicked(object sender, System.EventArgs e)
{
DependencyService.Get<IosCallerDialer>().StartCall();
}
Apart this I added all the classes defined in the document.I want only outgoing call. Is this proper way? I cant find any tutorials regarding callkit on xamarin. Any help is appreciated.
EDIT: I understand Callkit only for voip. So is there any other workaround like starting a timer when moves to phone app and stop timer when returns to app? Is it possible? Please provide any insights.
You can try the code below to detect the state of incoming call.
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
//
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
// You have 17 seconds to return from this method, or iOS will terminate your application.
//
public CTCallCenter c { get; set; }
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
c = new CTCallCenter();
c.CallEventHandler = delegate (CTCall call)
{
if (call.CallState == call.StateIncoming)
{
//start the timer
}
else if (call.CallState == call.StateDialing)
{
}
else if (call.CallState == call.StateConnected)
{
}
else if(call.CallState == call.StateDisconnected)
{
//end the timer
//use messagecenter to send duartion
MessagingCenter.Send<Object>(new Object(), "Hi");
}
};
return base.FinishedLaunching(app, options);
}
}
And any Where in Xamarin.forms:
MessagingCenter.Subscribe<Object>(this, "Hi", (sender) => {
// do something whenever the "Hi" message is sent
Console.WriteLine("hihihi");
});
Note: I haven't test it on my side yet as I don't have enough device. You can test it and let me know if it works.
Currently I am developing a Xamarin App which is using IdentityModel.OidcClient to authenticate against my server, and it is being done using the automatic mode presented on the documentation (https://github.com/IdentityModel/IdentityModel.OidcClient2). Everything is working just fine as var result = await client.LoginAsync();
is returning the LoginResult with the AccessToken, etc.
What I am trying to figure out is how the backbutton, the recent apps button (both on android) and the close button on ChromeCustomTabsBrowser should be handled since these three actions close the Ibrowser attached to the oidcClient without returning a response and will keep me stuck awaiting for a response preventing me to process with the rest of the code validations.
private async Task SignInAsync() {
IsBusy = true;
await Task.Delay(500);
try {
LoginResult result = await IdentityService.LoginAsync(new LoginRequest());
if (result == null) {
OnError(noInternetErrorMessage);
IsBusy = false;
return;
}
if (result.IsError) {
OnError(result.Error);
} else {
string userName = result.User.Claims.Where(claim => claim.Type == userNameClaimType).Select(claim => claim.Value).SingleOrDefault();
_UserToken = IdentityService.CreateOrUpdateUserToken(userName, result);
if (_UserToken != null) {
await NavigationService.NavigateToAsync<LockScreenViewModel>();
} else {
OnError(errorMessage);
}
}
} catch (Exception e) {
OnError(e.ToString());
}
IsBusy = false;
}
In the previous block of code I can't reach if (result == null) if those buttons where clicked which in turn will prevent me from removing the ActivityIndicator in the loginView and provide the login button to the user so he can try login again.
This happens because your IdentityService.LoginAsync() task is actually still waiting in the background for the custom tabs activity callback to happen, regardless of the fact that the custom tabs browser is no longer visible. Because the user closed before completing the login roundtrip, no callback will be triggered until the user completes the roundtrip in a future attempt. Each login attempt will create a new awaiting task, so the collection of waiting tasks will grow each time the user closes the custom tabs window prematurely.
At the time the user actually finishes a login roundtrip it becomes clear that the tasks are all still waiting, because they all at once unfreeze when the long awaited callback finally occurs. This poses another issue to handle, because all but the last task will result in an 'invalid state' oidc error result.
I resolved this by canceling the previous task just before starting a new login attempt. I added a TryCancel method to ChromeCustomTabsBrowser on a custom interface IBrowserExtra. In the ChromeCustomTabsBrowser.InvokeAsync implementation, a reference is kept to the TaskCompletionSource to be returned.
The next time the user clicks the sign in button, TryCancel is first invoked before ChromeCustomTabsBrowser.LoginAsync to unlock the previous login attempt still awaiting, using the kept reference.
To make this work, IsBusy=True should be postponed until after the custom tabs callback (custom tabs browser will be on top anyway), to keep the gui interactive in case the custom tabs close button was clicked. Otherwise the user will never be able to reattempt login.
Update: added sample code as requested.
public interface IBrowserExtra
{
void TryCancel();
}
public class ChromeCustomTabsBrowser : IBrowser, IBrowserExtra, IBrowserFallback
{
private readonly Activity _context;
private readonly CustomTabsActivityManager _manager;
private TaskCompletionSource<BrowserResult> _task;
private Action<string> _callback;
public ChromeCustomTabsBrowser()
{
_context = CrossCurrentActivity.Current.Activity;
_manager = new CustomTabsActivityManager(_context);
}
public Task<BrowserResult> InvokeAsync(BrowserOptions options)
{
var builder = new CustomTabsIntent.Builder(_manager.Session)
.SetToolbarColor(Color.Argb(255, 0, 0, 0))
.SetShowTitle(false)
.EnableUrlBarHiding()
.SetStartAnimations(_context, Android.Resource.Animation.SlideInLeft, Android.Resource.Animation.SlideOutRight)
.SetExitAnimations(_context, Android.Resource.Animation.SlideInLeft, Android.Resource.Animation.SlideOutRight);
var customTabsIntent = builder.Build();
// ensures the intent is not kept in the history stack, which makes
// sure navigating away from it will close it
customTabsIntent.Intent.AddFlags(ActivityFlags.NoHistory);
_callback = null;
_callback = url =>
{
UnsubscribeFromCallback();
_task.TrySetResult(new BrowserResult()
{
Response = url
});
};
SubscribeToCallback();
// Keep track of this task to be able to refer it from TryCancel later
_task = new TaskCompletionSource<BrowserResult>();
customTabsIntent.LaunchUrl(_context, Android.Net.Uri.Parse(options.StartUrl));
return _task.Task;
}
private void SubscribeToCallback()
{
OidcCallbackActivity.Callbacks += _callback;
}
private void UnsubscribeFromCallback()
{
OidcCallbackActivity.Callbacks -= _callback;
_callback = null;
}
void IBrowserExtra.TryCancel()
{
if (_callback != null)
{
UnsubscribeFromCallback();
}
if (_task != null)
{
_task.TrySetCanceled();
_task = null;
}
}
}
public class LoginService
{
private static OidcClient s_loginClient;
private Task<LoginResult> _loginChallengeTask;
private readonly IBrowser _browser;
private readonly IAppInfo _appInfo;
public LoginService(
IBrowser secureBrowser,
IBrowserFallback fallbackBrowser,
IAppInfo appInfo)
{
_appInfo = appInfo;
_browser = ChooseBrowser(appInfo, secureBrowser, fallbackBrowser);
}
private IBrowser ChooseBrowser(IAppInfo appInfo, IBrowser secureBrowser, IBrowserFallback fallbackBrowser)
{
return appInfo.PlatformSupportsSecureBrowserSession ? secureBrowser : fallbackBrowser as IBrowser;
}
public async Task<bool> StartLoginChallenge()
{
// Cancel any pending browser invocation task
EnsureNoLoginChallengeActive();
s_loginClient = OpenIdConnect.CreateOidcClient(_browser, _appInfo);
try
{
_loginChallengeTask = s_loginClient.LoginAsync(new LoginRequest()
{
FrontChannelExtraParameters = OpenIdConnectConfiguration.LoginExtraParams
});
// This triggers the custom tabs browser login session
var oidcResult = await _loginChallengeTask;
if (_loginChallengeTask.IsCanceled)
{
// task can be cancelled if a second login attempt was completed while the first
// was cancelled prematurely even before the browser view appeared.
return false;
}
else
{
// at this point we returned from the browser login session
if (oidcResult?.IsError ?? throw new LoginException("oidcResult is null."))
{
if (oidcResult.Error == "UserCancel")
{
// Graceful exit: user canceled using the close button on the browser view.
return false;
}
else
{
throw new LoginException(oidcResult.Error);
}
}
}
// we get here if browser session just popped and navigation is back at customer page
PerformPostLoginOperations(oidcResult);
return true;
}
catch (TaskCanceledException)
{
// swallow cancel exception.
// this can occur when user canceled browser session and restarted.
// Previous session is forcefully canceled at start of ExecuteLoginChallenge cauing this exception.
LogHelper.Debug($"'Login attempt was via browser roundtrip canceled.");
return false;
}
}
private void EnsureNoLoginChallengeActive()
{
if (IsLoginSessionStarted)
{
(_browser as IBrowserExtra)?.TryCancel();
}
}
private static bool IsLoginSessionStarted => s_loginClient != null;
}
I noticed that if I execute a query in Firebase and the database server is not reachable, the callback waits just forever (or until the server is reachable again).
Where this behavior is quite natural for the asynchronous approach used, it would nevertheless be useful to have an easy way to specify a timeout so you could inform the user about the status.
Is there such an option and I just missed it - or it really missing?
Or how would you solve this problem?
you can manage yourself a timer controller that after x seconds remove the listener to you firebase reference. It's very simple, just one line of code in android for example.
You can see the code for the web (Detaching Callbacks section):
https://www.firebase.com/docs/web/guide/retrieving-data.html
or for android (Detaching Callbacks section):
https://www.firebase.com/docs/android/guide/retrieving-data.html#section-detaching
same section for IOS ;)
As per today there is no timeout concept on those listeners. One option is to manage the timeout yourself.
This is how I do it when I also want to display a progress dialog while loading the content.
private void showProgressDialog(boolean show, long time) {
try {
if (progressDialog != null) {
if (show) {
progressDialog.setMessage("Cargando...");
progressDialog.show();
new Handler().postDelayed(new Runnable() {
public void run() {
if(progressDialog!=null && progressDialog.isShowing()) {
progressDialog.dismiss();
Toast.makeText(ActPreguntas.this, "Couldn't connect, please try again later.", Toast.LENGTH_LONG).show();
}
}
}, time);
} else {
progressDialog.dismiss();
}
}
}catch(IllegalArgumentException e){
}catch(Exception e){
}
}
So when you make a request to Firebase you call showProgressDialog(true,5000) and after 5 seconds if the dialog stills there is because it could not connect and you then do what you have to as per the timeout.
On the callback of the Firebase listener you do this showProgressDialog(false,0)
Hope it helps.
Here's my solution for the Firebase iOS SDK, this may be helpful for others:
extension DatabaseReference {
func observe(_ eventType: DataEventType, timeout: TimeInterval, with block: #escaping (DataSnapshot?) -> Void) -> UInt {
var handle: UInt!
let timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { (_) in
self.removeObserver(withHandle: handle)
block(nil)
}
handle = observe(eventType) { (snapshot) in
timer.invalidate()
block(snapshot)
}
return handle
}
}
Usage:
database.child("users").observe(.value, timeout: 30) { (snapshot) in
guard let snapshot = snapshot else {
// Timeout!
return
}
// We got data within the timeout, so do something with snapshot.value
}
I would suggest simply using a thread?
Allow yourself to assign your call to Firebase from within a thread instance, then in the rare event that the write to Firebase takes too long you can just cancel the thread?
let thread = NSThread(target:self, selector:#selector(uploadToFirebase), object:nil)
. . .
func uploadToFirebase(data: Dictionary) {
// Do what you need to here. Just an example
db.collection("posts").document("some unique post id").setData([
"name": "John",
"likes": 0
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
}
Then just create a timer that cancels the thread if the timer fires. If not, just cancel the timer.
If you're using the Firebase SDK v6.5.0 and above, you can use FirebaseOptions's setConnectTimeout (https://firebase.google.com/docs/reference/admin/java/reference/com/google/firebase/FirebaseOptions.Builder.html#setConnectTimeout(int)).
Sample:
Integer connectTimeoutinMillis = 6000; //6 seconds
FirebaseOptions firebaseOptions = FirebaseOptions.builder()
.setCredentials(credentials)
.setDatabaseUrl(Application.firebaseSDKDatabaseUrl)
.setConnectTimeout(connectTimeoutinMillis)
.build();
FirebaseApp.initializeApp(firebaseOptions);