I am trying to make an when where I provide a list of numbers and then it starts calling them one by one. So, the flow should be something like this:
for each number in list
ask user if they want to call. If yes:
open dialer
call number
come back to the app
If no:
break the loop
I have tried this:
public partial class MainPage : ContentPage
{
List<string> numbers = new List<string>();
//Consider the list is already filled with different numbers
public MainPage()
{
InitializeComponent();
}
private async Task Button_ClickedAsync(object sender, EventArgs e)
{
bool answer = false;
foreach(var number in numbers)
{
answer = await DisplayAlert("Question?", $"Call {number}?", "Yes", "No");
if (answer)
{
var phoneDialer = CrossMessaging.Current.PhoneDialer;
phoneDialer.MakePhoneCall(number);
}
else
break;
}
}
}
I am using the Messaging Plugin to make the call. The problem is that when I click the button that calls the Button_ClickedAsync method, I get to call the first number in my list, after that when I end the call and return to the app, the loop doesn't continue after that. So, I have to click the button again and the loop starts over from the very first number in the list (As expected). How can I make it continue the list when I return to my app so I don't have to click the button each time?
I have also tried with Intents but I get the same result:
private async Task Button_ClickedAsync(object sender, EventArgs e)
{
bool answer = false;
foreach(var number in numbers)
{
answer = await DisplayAlert("Question?", $"Call {number}?", "Yes", "No");
if (answer)
{
var uri = Android.Net.Uri.Parse($"tel:{number}");
var intent = new Intent(Intent.ActionDial, uri);
Android.App.Application.Context.StartActivity(intent);
}
else
break;
}
While calling your application is getting to the background and is resumed when you hang up the call. If you don't save your state when the app is pushed to the background it will just start it like it's a fresh start.
In the Application class there are methods to override which you can use to save the state and check the state on resume. https://learn.microsoft.com/en-gb/xamarin/xamarin-forms/app-fundamentals/app-lifecycle
So my suggestion would be to store the state of the current phone number you're calling and on the resume check if there was a previous number called and continue from that number in your list.
Related
I'm loading the page's data in the OnAppearing method, but this causes loading data in situations where it doesn't change, like calling PopupAsync() from another page. so I thought the messaging center would help. I made a flag field in the page, and subscribed to any message coming from outside to decide whether to update data or not,
for example this from the MenuViewModel (when the user first opens the page, so I need to load data):
var p = new FeedbackListPage();
MessagingCenter.Send(this, "loadData", "1");
await Navigation.PushAsync(p);
and in the FeedbackListPage's constructor:
InitializeComponent();
BindingContext = vm = new FeedbackViewModel(Navigation);
MessagingCenter.Subscribe<string>(this, "loadData", ( _loadData) =>
{
loadDataStr = _loadData;
});
and in the OnAppearing:
protected override void OnAppearing()
{
base.OnAppearing();
if (loadDataStr=="1")
vm.OnLoadFeedbacks();
}
the problem is that the Subscribe's action is never called!
Solution:
The API for MessagingCenter:
1.Subscribe<TSender> (object subscriber, string message, Action<TSender> callback, TSender source = null)
2.Subscribe<TSender, TArgs> (object subscriber, string message,Action<TSender, TArgs> callback, TSender source = null)
So, if you want to pass an Argument using MessageCenter, you should define both Sender and Args:
MessagingCenter.Subscribe<MainPage,string>(this, "loadData", (sender,_loadData) =>
{
loadDataStr = _loadData;
});
Refer: messaging-center
You can try the following:
use MessagingCenter.Send to send the signal when you want to update the data from any page and then in your ViewModel's constructor use MessagingCenter.Subscribe to perform the needed action
Send:
MessagingCenter.Send<namespace.App>((namespace.App)Xamarin.Forms.Application.Current, "update");
Subscribe:
MessagingCenter.Subscribe<namespace.App>((namespace.App)Application.Current, "update", (sender) => {
// update - get data
});
I've a problem where I send message once and Subscriber is called once but next time it is called twice and so on... Here's my code.
This is message sender
public void OnSuccess(Java.Lang.Object result)
{
UploadTask.TaskSnapshot taskSnapShot = (UploadTask.TaskSnapshot)result;
string downloadURL = taskSnapShot.DownloadUrl.ToString();
string fileName = taskSnapShot.Metadata.Name;
GBPaperReceipt.Model.ImageFile imageFile = new Model.ImageFile
{
FileName = fileName,
FilePath = downloadURL
};
MessagingCenter.Send((App)Xamarin.Forms.Application.Current, MessageStrings.ImageUploadEvent, imageFile);
//save this live storage image url in receipt table
//MessagingCenter.Send<Xamarin.Forms.Application, string>((Xamarin.Forms.Application)Xamarin.Forms.Application.Current, ChatModuleConstant.UploadMediaEvent, downloadURL);
}
This is message receiver
MessagingCenter.Subscribe<App, ImageFile>((App)Application.Current, MessageStrings.ImageUploadEvent,async (a, imageFile) =>
{
_viewModel.Receipt.ImagePath = imageFile.FilePath;
_viewModel.Receipt.ImageName = imageFile.FileName;
try
{
await DependencyService.Get<IReceiptService>().SaveReceipt(_viewModel.Receipt);
}
catch (Exception ex)
{
await DisplayAlert(
"Error!", ex.Message, "OK");
}
DependencyService.Get<ICamera>().DeletePhoto(_viewModel._imageToBeDeletedOnSaveCommand);
Dialogs.HideLoading();
Application.Current.MainPage = new NavigationPage(new DashboardPage());
});
Unsubscription
protected override void OnDisappearing()
{
base.OnDisappearing();
MessagingCenter.Unsubscribe<App, string>((App)Application.Current, MessageStrings.ErrorEvent);
MessagingCenter.Unsubscribe<App, string>((App)Application.Current, MessageStrings.ImageUploadEvent);
}
Especially when using your page inside a navigationpage, your subscription event will be added whenever the page comes into view. If you navigate back and forward a couple of times, your subscription to the messagingcenter will be added several times causing your event to double fire.
The safest way is to subscribe in the page constructor and even in that case it can be necessary to unsubscribefirst and then subscribe.
Your Appearing/Disappearing approach might work as well, however I am not entirely sure if the Appearing/Disappearing methods give you any guarantee to fire.
However, you also might try moving your unsubscriptions in front of base.OnDisappearing(), since you should unsubscribe before calling the base class to do the internal dismantling of your page.
If that doesn't work, subscribe in the constructor.
I'm at a loss on this one. I'm unable to delete a node in Firebase either from the web app or through the Firebase console. When the /courses/{courseId}/members/{uid} node is removed, either through set(null) or remove(), the user's information is added immediately.
I do have two cloud functions updating the seats node so we can keep track of space, but neither of those point at the ../{uid} endpoint. I've also gone through my web app code to make sure there were no on_delete events writing to the tree. Any clues as to what's happening?
UPDATE
After going backward in my commit tree, I was able to confirm that it was the below cloud function disallowing deletes from the tree. So, my question now becomes, what in this function causing the behavior? I can't see anything that would re-write data. My database structure is below.
/courses ref
courses: {
someStringId1234: {
members: {
uid12345: {
code: 'someString',
email: 'some#email.com'
},
uid67890: { ... }
},
seats: 10
},
{ ... }
}
This cloud function watches for changes to the uid item. It grabs the parent node (members) and then updates the count in seats. For some reason, this is re-writing the previously deleted key and setting it to 0.
countRegistrations, firebase cloud function
exports.countRegistrations = functions.database.ref('/courses/{courseId}/members/{uid}').onWrite(
(change) => {
// get the `members` node for the item
const collectionRef = change.after.ref.parent;
console.log('collectionRef', collectionRef)
// get the `seats` key for updating
const countRef = collectionRef.parent.child('seats');
console.log('countRef', countRef)
let increment;
// If the ID is there after but not before, remove one seat
if (change.after.exists() && !change.before.exists()) {
increment = -1;
// if the ID is not there after, but there before, add one seat
} else if (!change.after.exists() && change.before.exists()) {
increment = 1;
} else {
// Nothing to change
return null;
}
// Return the promise from countRef.transaction() so the function
// waits for this async event to complete before it exits.
return countRef.transaction((current) => {
console.log('Line 38', current) // debugging, confirms the correct refs are grabbed by the function
return (current || 0) + increment;
}).then(() => {
return
});
});
Just for fun, here's what happens when I try to delete the node directly in the console. The database rules allow writing if you're logged in.
I had the same issue. Turns out that by deleting that node would cause too many firebase functions to execute. Try use the delete command in the CLI, it might give you more information on the error. firebase database:remove <//path to node>
I had same issue, and I just fixed it. The error occurred because of the node data it is using.
Please check where you use the node current, and break or sign out. Then, it is possible to delete the node.
Explanation: If the node is user info and current the user is logged in, it is impossible to delete the node. Continuously, re-created node as long as deleting node.
For this scenario, i am using handler to delay the refresh of my activity with 2-3 seconds, If anyone is getting this error then make sure:-
Do not make the refresh of an activity or display (reloading the database)
Use handler with 2-3 seconds delay, then the logged in user can't auto remake the node.
How i used to delete the node:-
myViewHolder.deleteicon.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String childvalue = list.get(i).getPosition();
Log.d(TAG, "onClick: "+ childvalue);
FirebaseDatabase.getInstance().getReference()
.child("Submission/Rules").child(categorynametext).child(user.getUid()).child(childvalue).removeValue()
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
Toast.makeText(context, "You have removed successfully", Toast.LENGTH_LONG).show();
try
{
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
///Here i am refreshing the activity
((Activity)context).finish();
((Activity)context).overridePendingTransition( 0, 0);
((Activity)context).startActivity(((Activity) context).getIntent());
((Activity)context).overridePendingTransition( 0, 0);
}
}, 3000);
}
catch (Exception e){
e.printStackTrace();
}
} else {
//enter msg or enter your code which you want to show in case of value is not remove properly or removed failed.
Toast.makeText(context, "There is an error while removing the rule, Please try again later", Toast.LENGTH_LONG).show();
}
}
});
}
});
UPDATE:
After working more in this, i found i was also getting the same error, because of using addValueEventListener< in this case it is auto adding the data while deleting or adding something in the database and if you use addListenerForSingleValueEvent, then there will not any problem while adding or deleting the data.
Final Conclusion:
addValueEventListener (If you want to refresh the page everytime any add or deletion occurs
addListenerForSingleValueEvent(If you don't want to refresh the page while adding or deleting something)
Using the Twitch API and vert.x - I'm looking to continuously send requests to Twitch's API using a WebClient and Twitch's cursor response to go page by page. However I'm not sure how to go back and keep doing queries until a condition is met due to vert.x's asynchronous nature.
Here's my code so far
public void getEntireStreamList(Handler<AsyncResult<JsonObject>> handler) {
JsonObject data = new JsonObject();
getLiveChannels(100, result -> {
if(result.succeeded()) {
JsonObject json = result.result();
String cursor = json.getJsonObject("pagination").getString("cursor");
data.put("data", json.getJsonArray("data"));
if(json.getJsonArray("data").size() < 100) { // IF NOT LAST PAGE
// GO BACK AND DO AGAIN WITH CURSOR IN REQUEST
}
handler.handle(Future.succeededFuture(data));
} else
handler.handle(Future.failedFuture(result.cause()));
});
}
Ideally I'd be able to call getLiveChannels with the cursor String from the previous request to continue the search.
You will need to use Future composition.
Here's my code for your problem:
public void getEntireStreamList(Handler<AsyncResult<JsonObject>> handler) {
JsonArray data = new JsonArray();
// create initial Future for first function call
Future<JsonObject> initFuture = Future.future();
// complete Future when getLiveChannels completes
// fail on exception
getLiveChannels(100, initFuture.completer());
// Create a callback that returns a Future
// for composition.
final AtomicReference<Function<JsonObject, Future<JsonObject>>> callback = new AtomicReference<>();
// Create Function that calls composition with itself.
// This is similar to recursion.
Function<JsonObject, Future<JsonObject>> cb = new Function<JsonObject, Future<JsonObject>>() {
#Override
public Future<JsonObject> apply(JsonObject json) {
// new Future to return
Future<JsonObject> f = Future.future();
// Do what you wanna do with the data
String cursor = json.getJsonObject("pagination").getString("cursor");
data.addAll(json.getJsonArray("data"));
// IF NOT LAST PAGE
if(json.getJsonArray("data").size() == 100) {
// get more live channels with cursor
getLiveChannels(100, cursor, f.completer());
// return composed Future
return f.compose(this);
}
// Otherwise return completed Future with results.
f.complete(new JsonObject().put("data", data));
return f;
}
};
Future<JsonObject> composite = initFuture.compose(cb);
// Set handler on composite Future (ALL composed futures together)
composite.setHandler(result -> handler.handle(result));
}
The code + comments should speak for themselves if you read the Vert.x docs on sequential Future composition.
I am doing my first tests with SignalR. I am toying with chat messages, but that's only a first step to replace all the polling from client to server which I have today on my site.
I have a lot of scenarios where I want to notify certain users either by their login or by their ID. The idea is that I am adding each user to two groups as soon as he connects. I do this in OnConnected and that event is called.
When I send a chat message, I have two modes: either public or personal. If it is personal the sender is notified and the recipient should be notified. The sender gets a message but the group never does. It seems to be impossible to found out how many members a group has.
Any ideas what's going wrong here?
public class GlobalHub:Hub
{
private Users user;
private void AuthenticateUser()
{
var ydc = new MyDataContext();
user = ydc.Users.First(u => u.Login == HttpContext.Current.User.Identity.Name);
}
public override Task OnConnected()
{
var ydc = new MyDataContext();
user = ydc.Users.First(u => u.Login == HttpContext.Current.User.Identity.Name);
Groups.Add(Context.ConnectionId, user.Login);
Groups.Add(Context.ConnectionId, user.ID.ToString());
return base.OnConnected();
}
public void SendChatMessage(string message, string recipient)
{
AuthenticateUser();
var cm = ChatController.AddChatMessage(user.Login, user.ID, recipient, tmessage);
if (recipient != "")
{
Clients.Caller.NewMessage(cm);
Clients.Group(recipient).NewMessage(cm);
}
else
{
Clients.All.NewMessage(cm);
}
}
}
It looks like that Groups.Add does not immediately join the connection to the group, but instead returns a Task, that needs to be started. Try returning the result of Groups.Add as result of OnConnectedMethod.
See also more detailed explanation at: https://stackoverflow.com/a/15469038/174638