OnInitializedAsync() Issue with Blazor Telerik - c#

Im using Telerik Blazor controls, I am using mt API to return data which works fine when I check it from an API perspective. My web app has issues as it errors as I believe there is no data. To my knowledge this is due to OnInitializedAsync(). Below is my code:
<TelerikScheduler Data="#HolidayPlanners" #bind-Date="#StartDate" #bind-View="#selectedView" Height="100%" Class="Scheduler" OnCreate="#AddAppointment"
OnUpdate="#UpdateAppointment" OnDelete="#DeleteAppointment"
AllowCreate="true" AllowDelete="true" AllowUpdate="true"
IdField="#(nameof(UvwHolidayPlanner.Pk))"
StartField="#(nameof(UvwHolidayPlanner.StartDate))"
EndField="#(nameof(UvwHolidayPlanner.EndDate))"
TitleField="#(nameof(UvwHolidayPlanner.Title))"
DescriptionField="#(nameof(UvwHolidayPlanner.Description))"
IsAllDayField="#(nameof(UvwHolidayPlanner.IsAllDay))"
RecurrenceRuleField="#(nameof(UvwHolidayPlanner.RecurrenceRule))"
RecurrenceExceptionsField="#(nameof(UvwHolidayPlanner.RecurrenceExceptions))"
RecurrenceIdField="#(nameof(UvwHolidayPlanner.RecurrenceFk))">
<SchedulerViews>
<SchedulerMonthView></SchedulerMonthView>
</SchedulerViews>
#code {
public string _URL = String.Empty;
IEnumerable<UvwHolidayPlanner> HolidayPlanners { get; set; }
DateTime StartDate = DateTime.Now;
SchedulerView selectedView { get; set; } = SchedulerView.Month;
protected override async Task OnInitializedAsync()
{
_URL = settingsAccessor.AllClientSettings().BaseServiceURI;
HolidayPlanners = (await http.CreateClient("ClientSettings").GetFromJsonAsync<List<UvwHolidayPlanner>>($"{_URL}/lookup/HolidayPlanner"))
.OrderBy(t => t.Title)
.ToList();
StateHasChanged();
}
//private void LoadData()
//{
//Appointments = appointmentService.GetAppointments();
//}
void UpdateAppointment(SchedulerUpdateEventArgs args)
{
//appointmentService.UpdateAppointment((AppointmentDto)args.Item);
//LoadData();
}
void AddAppointment(SchedulerCreateEventArgs args)
{
//appointmentService.CreateAppointment((AppointmentDto)args.Item);
//LoadData();
}
void DeleteAppointment(SchedulerDeleteEventArgs args)
{
//appointmentService.DeleteAppointment((AppointmentDto)args.Item);
//LoadData();
}
}
My HolidayPlanners property seems to return null & it errors. Unsure if this is because the UI Components have already initialized.
Below is a message within the 404 error unsure if thats an issue:
ProjectManager.Pages.Pages__Host.<ExecuteAsync>b__15_1() in _Host.cshtml
<component type="typeof(App)" render-mode="ServerPrerendered" />
I have tried render-mode="Server" & "Static"

Related

Updating an object asynchronously with another one

I'm trying to update an object with another object asynchronously. I'm trying to get the CustomerId value from Statues and then use it to call a specific customer and pass those values into PreviousStatuses. Then update the StatusToAdd with PreviousStatuses. If I pass Statues to StatusToAdd the values update. However, it's the wrong customer id. That's why I'm using PreviousStatuses.
This is the error I get:
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Object reference not set to an instance of an object.
System.NullReferenceException: Object reference not set to an instance of an object.
at DSPDRewrite.Pages.Popups.AddStatusComponent.UpdateValues(String id)
at DSPDRewrite.Pages.Popups.AddStatusComponent.OnInitializedAsync()
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)
[Parameter]
public int CustomerId { get; set; }
[CascadingParameter]
public Task<AuthenticationState> AuthState { get; set; }
public Status Statuses;
public Status PreviousStatuses;
//IList<Status> PreviousStatuses;
public Dspd1056Status StatusToAdd = new Dspd1056Status();
public Customer customer;
public int AccountStatusId = 0;
protected override async Task OnInitializedAsync()
{
Statuses = await dataService.Get1056StatusById(CustomerId);
//int id = Int32.Parse(Statuses.CustomerId);
// Statuses = await dataService.Get1056StatusById(id);
Console.WriteLine(Statuses.CustomerId);
await UpdateValues(Statuses.CustomerId);
}
async Task UpdateValues(string id)
{
PreviousStatuses = await dataService.Get1056StatusById(Int32.Parse((id)));
StatusToAdd.AccountCurrent = PreviousStatuses.AccountCurrent;
StatusToAdd.StartDate = PreviousStatuses.StartDate;
StatusToAdd.EndDate = PreviousStatuses.EndDate;
StatusToAdd.Units = PreviousStatuses.Units;
StatusToAdd.Ppc = PreviousStatuses.Ppc;
StatusToAdd.EndStatus = PreviousStatuses.EndStatus;
StatusToAdd.ContinuallyFunded = PreviousStatuses.ContinuallyFunded;
StatusToAdd.AnnualUnits = PreviousStatuses.AnnualUnits;
StatusToAdd.Elg = PreviousStatuses.Elg;
StatusToAdd.ReceiptDate = PreviousStatuses.ReceiptDate;
StatusToAdd.RahTripsFunded = PreviousStatuses.RahTripsFunded;
StatusToAdd.Rate = PreviousStatuses.Rate;
StatusToAdd.AccountTotal = PreviousStatuses.AccountTotal;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
if (CustomerId != 0)
{
customer = await dataService.GetCustomerById((int)CustomerId);
StateHasChanged();
}
}
}
A few things to consider:
OnInitializedAsync
Can your .Get1056StatusById() method return null (e.g. if CustomerId is not found)? If so that appears to be the cause of the error: your code calls
await UpdateValues(Statuses.CustomerId);
That suggests Statuses is null. I would add a null check to this code to handle that case. Not sure why the Console.WriteLine didn't throw the null reference first, I assume the sample code didn't have it when the error was thrown.
A few other points: fields and methods in Components should normally be private or protected. If you are going to expose outside the Component they should be properties. Methods are rarely called outside a component. My suggested changes would be:
[Parameter] public int CustomerId { get; set; }
[CascadingParameter] public Task<AuthenticationState> AuthState { get; set; }
Status Statuses;
Status PreviousStatuses;
Dspd1056Status StatusToAdd = new Dspd1056Status();
Customer customer;
int AccountStatusId = 0;
protected override async Task OnInitializedAsync()
{
Statuses = await dataService.Get1056StatusById(CustomerId);
if(Statuses != null)
{
Console.WriteLine(Statuses.CustomerId);
await UpdateValues(Statuses.CustomerId);
}
}
async Task UpdateValues(string id)
{
PreviousStatuses = await dataService.Get1056StatusById(Int32.Parse((id)));
// rest...
Your component HTML should obviously check for a null Statuses before it tries to render:
#if(Statuses != null)
{
<p>Customer is #Statuses.CustomerId</p>
}
This is such a frequently used pattern in Blazor apps I usually use a <NotNull> component:
https://gist.github.com/conficient/eab57ade2587104d71ac6f26ddfd4865

Blazor Textfield Oninput User Typing Delay

How can I add a delay to an event (OnInput) in Blazor ?For example, if a user is typing in the text field and you want to wait until the user has finished typing.
Blazor.Templates::3.0.0-preview8.19405.7
Code:
#page "/"
<input type="text" #bind="Data" #oninput="OnInputHandler"/>
<p>#Data</p>
#code {
public string Data { get; set; }
public void OnInputHandler(UIChangeEventArgs e)
{
Data = e.Value.ToString();
}
}
Solution:
There is no single solution to your question. The following code is just one approach. Take a look and adapt it to your requirements. The code resets a timer on each keyup, only last timer raises the OnUserFinish event.
Remember to dispose timer by implementing IDisposable
#using System.Timers;
#implements IDisposable;
<input type="text" #bind="Data" #bind:event="oninput"
#onkeyup="#ResetTimer"/>
<p >UI Data: #Data
<br>Backend Data: #DataFromBackend</p>
#code {
public string Data { get; set; } = string.Empty;
public string DataFromBackend { get; set; } = string.Empty;
private Timer aTimer = default!;
protected override void OnInitialized()
{
aTimer = new Timer(1000);
aTimer.Elapsed += OnUserFinish;
aTimer.AutoReset = false;
}
void ResetTimer(KeyboardEventArgs e)
{
aTimer.Stop();
aTimer.Start();
}
private async void OnUserFinish(Object? source, ElapsedEventArgs e)
{
// https://stackoverflow.com/a/19415703/842935
// Call backend
DataFromBackend = await Task.FromResult( Data + " from backend");
await InvokeAsync( StateHasChanged );
}
void IDisposable.Dispose()
=>
aTimer?.Dispose();
}
Use case:
One example of use case of this code is avoiding backend requests, because the request is not sent until user stops typing.
Running:
This answer is the middle ground between the previous answers, i.e. between DIY and using a full-blown reactive UI framework.
It utilizes the powerful Reactive.Extensions library (a.k.a. Rx), which in my opinion is the only reasonable way to solve such problems in normal scenarios.
The solution
After installing the NuGet package System.Reactive you can import the needed namespaces in your component:
#using System.Reactive.Subjects
#using System.Reactive.Linq
Create a Subject field on your component that will act as the glue between the input event and your Observable pipeline:
#code {
private Subject<ChangeEventArgs> searchTerm = new();
// ...
}
Connect the Subject with your input:
<input type="text" class="form-control" #oninput=#searchTerm.OnNext>
Finally, define the Observable pipeline:
#code {
// ...
private Thing[]? things;
protected override async Task OnInitializedAsync() {
searchTerm
.Throttle(TimeSpan.FromMilliseconds(200))
.Select(e => (string?)e.Value)
.Select(v => v?.Trim())
.DistinctUntilChanged()
.SelectMany(SearchThings)
.Subscribe(ts => {
things = ts;
StateHasChanged();
});
}
private Task<Thing[]> SearchThings(string? searchTerm = null)
=> HttpClient.GetFromJsonAsync<Thing[]>($"api/things?search={searchTerm}")
}
The example pipeline above will...
give the user 200 milliseconds to finish typing (a.k.a. debouncing or throttling the input),
select the typed value from the ChangeEventArgs,
trim it,
skip any value that is the same as the last one,
use all values that got this far to issue an HTTP GET request,
store the response data on the field things,
and finally tell the component that it needs to be re-rendered.
If you have something like the below in your markup, you will see it being updated when you type:
#foreach (var thing in things) {
<ThingDisplay Item=#thing #key=#thing.Id />
}
Additional notes
Don't forget to clean up
You should properly dispose the event subscription like so:
#implements IDisposable // top of your component
// markup
#code {
// ...
private IDisposable? subscription;
public void Dispose() => subscription?.Dispose();
protected override async Task OnInitializedAsync() {
subscription = searchTerm
.Throttle(TimeSpan.FromMilliseconds(200))
// ...
.Subscribe(/* ... */);
}
}
Subscribe() actually returns an IDisposable that you should store and dispose along with your component. But do not use using on it, because this would destroy the subscription prematurely.
Open questions
There are some things I haven't figured out yet:
Is it possible to avoid calling StateHasChanged()?
Is it possible to avoid calling Subscribe() and bind directly to the Observable inside the markup like you would do in Angular using the async pipe?
Is it possible to avoid creating a Subject? Rx supports creating Observables from C# Events, but how do I get the C# object for the oninput event?
I have created a set of Blazor components. One of which is Debounced inputs with multiple input types and much more features. Blazor.Components.Debounce.Input is available on NuGet.
You can try it out with the demo app.
Note: currently it is in Preview. Final version is coming with .NET 5. release
I think this is the better solution for me, I used it for searching.
Here's the code that I used.
private DateTime timer {
get;
set;
} = DateTime.MinValue;
private async Task SearchFire(ChangeEventArgs Args) {
if (timer == DateTime.MinValue) {
timer = DateTime.UtcNow;
} else {
_ = StartSearch(Args);
timer = DateTime.UtcNow;
}
}
private async Task StartSearch(ChangeEventArgs Args) { //2000 = 2 seconeds you can change it
await Task.Delay(2000);
var tot = TimeSpan.FromTicks((DateTime.UtcNow - timer).Ticks).TotalSeconds;
if (tot > 2) {
if (!string.IsNullOrEmpty(Args.Value.ToString())) { //Do anything after 2 seconds.
//reset timer after finished writhing
timer = DateTime.MinValue;
} else {}
} else {}
}
You can avoid bind the input. Just set #oninput
<Input id="theinput" #oninput="OnTextInput" />
#code {
public string SomeField { get; set; }
public void OnTextInput(ChangeEventArgs e)
{
SomeField = e.Value.ToString();
}
}
and set initial value in javascript (if there is).
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("setInitialValueById", "theinput", SomeField);
}
}
The setInitialValueById method:
window.setInitialValueById = (elementId, value) => {
document.getElementById(elementId).value = value;}
This will resolve the known input lag issue in blazor. You can set a label value with delay if it's the case:
public async Task OnTextInput(ChangeEventArgs e)
{
var value = e.Value.ToString();
SomeField = value;
await JSRuntime.InvokeVoidAsync("setLabelValue", value);
}
The setLabelValue method:
let lastInput;
window.setLabelValue = (value) => {
lastInput = value;
setTimeout(() => {
let inputValue = value;
if (inputValue === lastInput) {
document.getElementById("theLabelId").innerHTML = inputValue;
}
}, 2000);
}

Why does HttpClient GetAsync() not return in Xamarin?

I'm new to Xamarin and I'm trying to create a cross-platform app where users can login using a JSON API call. A token is then returned on a successful login attempt which I can use in other API's to display user data.
It works when I use the same code in a console application, but when I run it in Xamarin the code after await client.GetAsync(url) is never reached and after a while the application breaks and I get an unknown error. Am I experiencing a deadlock?
private async void loginButton_Click(object sender, EventArgs e)
{
var login = await loginAPI(LoginPage.nameEntry.Text, LoginPage.passEntry.Text);
if (login.state == "success")
{
...
}
else
{
...
}
}
public static async Task<LoginData> loginAPI(String username, String password)
{
try
{
using (var client = new HttpClient())
{
var loginUrl = new Uri("https://my-api/login?username=" + username + "&password=" + password);
var result = await client.GetAsync(loginUrl);
return JsonConvert.DeserializeObject<LoginData>(await result.Content.ReadAsStringAsync());
}
}
catch (Exception e)
{
return null;
}
}
public class LoginData
{
[JsonProperty("state")]
public String state { get; set; }
[JsonProperty("token")]
public String token { get; set; }
}

How to use signalr in Android

I am trying to integrate signalR in android app but no luck. I've been looking at various links but none of them provide proper information about implementation.
I've the following questions.
SignalR integration has to be done inside Service/Intent Service?
If we want to receive response via same calling method then how to get?
I've added three libraries i.e signalr android,signalr client and gson but unable to understand how code works, no proper documentation is available to understand the code.
Some of the questions asked but not much information
SignalR in Android Studio
Unable to implement p2p chat using SignalR in Android
If anyone experienced in signal for native apps, it would be very helpful for me.
Update
public class SignalRService extends Service {
private static final String TAG = "Service";
private HubConnection mHubConnection;
private HubProxy mHubProxy;
private Handler mHandler; // to display Toast message
private final IBinder mBinder = new LocalBinder();
private SharedPreferences sp;
#Override
public void onCreate() {
super.onCreate();
Utility.showLog(TAG, "Service Created");
sp = getSharedPreferences(Utility.SHARED_PREFS, MODE_PRIVATE);
mHandler = new Handler(Looper.myLooper());
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
int result = super.onStartCommand(intent, flags, startId);
startSignalR();
return result;
}
#Override
public IBinder onBind(Intent intent) {
startSignalR();
return mBinder;
}
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
public SignalRService getService() {
// Return this instance of SignalRService so clients can call public methods
return SignalRService.this;
}
}
/**
* method for clients (activities)
*/
public void sendMessage() {
String SERVER_METHOD_SEND = "iAmAvailable";
final String string = new String();
mHubProxy.invoke(new String(), SERVER_METHOD_SEND, sp.getString("user_id", null), sp.getString("pass", null), "TransMedic").done(new Action() {
#Override
public void run(Object o) throws Exception {
Utility.showLog(TAG, o.toString());
}
}).onError(new ErrorCallback() {
#Override
public void onError(Throwable throwable) {
}
});
}
private void startSignalR() {
Platform.loadPlatformComponent(new AndroidPlatformComponent());
String serverUrl = "http://transit.alwaysaware.org/signalr";
mHubConnection = new HubConnection(serverUrl);
String SERVER_HUB_CHAT = "ChatHub";
mHubProxy = mHubConnection.createHubProxy(SERVER_HUB_CHAT);
ClientTransport clientTransport = new ServerSentEventsTransport(mHubConnection.getLogger());
SignalRFuture<Void> signalRFuture = mHubConnection.start(clientTransport);
try {
signalRFuture.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return;
}
sendMessage();
}
#Override
public void onDestroy() {
mHubConnection.stop();
super.onDestroy();
}
}
UPDATE 2018:
If you are using SignalR.net Core use this library otherwise you will get error on connection.
SERVER SIDE:
The following is my sample server-side code, you can pay attention to public void Send(string message) and public void SendChatMessage(string to, string message).
Server-side app: public void SendChatMessage(string to, string message)
Android client app: mHubProxy.invoke("SendChatMessage", receiverName, message);
Server-side app: public void Send(string message)
Android client app: mHubProxy.invoke("Send", message);
namespace SignalRDemo
{
public class ChatHub : Hub
{
private static ConcurrentDictionary<string, string> FromUsers = new ConcurrentDictionary<string, string>(); // <connectionId, userName>
private static ConcurrentDictionary<string, string> ToUsers = new ConcurrentDictionary<string, string>(); // <userName, connectionId>
private string userName = "";
public override Task OnConnected()
{
DoConnect();
Clients.AllExcept(Context.ConnectionId).broadcastMessage(new ChatMessage() { UserName = userName, Message = "I'm Online" });
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
if (stopCalled) // Client explicitly closed the connection
{
string id = Context.ConnectionId;
FromUsers.TryRemove(id, out userName);
ToUsers.TryRemove(userName, out id);
Clients.AllExcept(Context.ConnectionId).broadcastMessage(new ChatMessage() { UserName = userName, Message = "I'm Offline" });
}
else // Client timed out
{
// Do nothing here...
// FromUsers.TryGetValue(Context.ConnectionId, out userName);
// Clients.AllExcept(Context.ConnectionId).broadcastMessage(new ChatMessage() { UserName = userName, Message = "I'm Offline By TimeOut"});
}
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
DoConnect();
Clients.AllExcept(Context.ConnectionId).broadcastMessage(new ChatMessage() { UserName = userName, Message = "I'm Online Again" });
return base.OnReconnected();
}
private void DoConnect()
{
userName = Context.Request.Headers["User-Name"];
if (userName == null || userName.Length == 0)
{
userName = Context.QueryString["User-Name"]; // for javascript clients
}
FromUsers.TryAdd(Context.ConnectionId, userName);
String oldId; // for case: disconnected from Client
ToUsers.TryRemove(userName, out oldId);
ToUsers.TryAdd(userName, Context.ConnectionId);
}
public void Send(string message)
{
// Call the broadcastMessage method to update clients.
string fromUser;
FromUsers.TryGetValue(Context.ConnectionId, out fromUser);
Clients.AllExcept(Context.ConnectionId).broadcastMessage(new ChatMessage() { UserName = fromUser, Message = message });
}
public void SendChatMessage(string to, string message)
{
FromUsers.TryGetValue(Context.ConnectionId, out userName);
string receiver_ConnectionId;
ToUsers.TryGetValue(to, out receiver_ConnectionId);
if (receiver_ConnectionId != null && receiver_ConnectionId.Length > 0)
{
Clients.Client(receiver_ConnectionId).broadcastMessage(new ChatMessage() { UserName = userName, Message = message });
}
}
}
public class ChatMessage
{
public string UserName { get; set; }
public string Message { get; set; }
}
}
CLIENT SIDE:
If you have not read my answer at the following question:
SignalR integration in android studio
Then, here is my working basic code:
public class SignalRService extends Service {
private HubConnection mHubConnection;
private HubProxy mHubProxy;
private Handler mHandler; // to display Toast message
private final IBinder mBinder = new LocalBinder(); // Binder given to clients
public SignalRService() {
}
#Override
public void onCreate() {
super.onCreate();
mHandler = new Handler(Looper.getMainLooper());
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
int result = super.onStartCommand(intent, flags, startId);
startSignalR();
return result;
}
#Override
public void onDestroy() {
mHubConnection.stop();
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {
// Return the communication channel to the service.
startSignalR();
return mBinder;
}
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
public SignalRService getService() {
// Return this instance of SignalRService so clients can call public methods
return SignalRService.this;
}
}
/**
* method for clients (activities)
*/
public void sendMessage(String message) {
String SERVER_METHOD_SEND = "Send";
mHubProxy.invoke(SERVER_METHOD_SEND, message);
}
private void startSignalR() {
Platform.loadPlatformComponent(new AndroidPlatformComponent());
Credentials credentials = new Credentials() {
#Override
public void prepareRequest(Request request) {
request.addHeader("User-Name", "BNK");
}
};
String serverUrl = "http://192.168.1.100";
mHubConnection = new HubConnection(serverUrl);
mHubConnection.setCredentials(credentials);
String SERVER_HUB_CHAT = "ChatHub";
mHubProxy = mHubConnection.createHubProxy(SERVER_HUB_CHAT);
ClientTransport clientTransport = new ServerSentEventsTransport(mHubConnection.getLogger());
SignalRFuture<Void> signalRFuture = mHubConnection.start(clientTransport);
try {
signalRFuture.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return;
}
String HELLO_MSG = "Hello from Android!";
sendMessage(HELLO_MSG);
String CLIENT_METHOD_BROADAST_MESSAGE = "broadcastMessage";
mHubProxy.on(CLIENT_METHOD_BROADAST_MESSAGE,
new SubscriptionHandler1<CustomMessage>() {
#Override
public void run(final CustomMessage msg) {
final String finalMsg = msg.UserName + " says " + msg.Message;
// display Toast message
mHandler.post(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(), finalMsg, Toast.LENGTH_SHORT).show();
}
});
}
}
, CustomMessage.class);
}
}
Activity:
public class MainActivity extends AppCompatActivity {
private final Context mContext = this;
private SignalRService mService;
private boolean mBound = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setClass(mContext, SignalRService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
super.onStop();
}
public void sendMessage(View view) {
if (mBound) {
// Call a method from the SignalRService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
EditText editText = (EditText) findViewById(R.id.edit_message);
if (editText != null && editText.getText().length() > 0) {
String message = editText.getText().toString();
mService.sendMessage(message);
}
}
}
/**
* Defines callbacks for service binding, passed to bindService()
*/
private final ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to SignalRService, cast the IBinder and get SignalRService instance
SignalRService.LocalBinder binder = (SignalRService.LocalBinder) service;
mService = binder.getService();
mBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
CustomMessage Class:
public class CustomMessage {
public String UserName;
public String Message;
}
You can also see my sample client project at this GitHub link
UPDATE FOR RESPONSE FROM INVOKE:
I have just added new sample methods:
Server side:
public string iAmAvailable(string username, string password, string message)
{
return "BNK Response for testing Android INVOKE";
}
Client side:
mHubProxy.invoke(String.class, "iAmAvailable", "username", "password", "TransMedic").done(new Action<String>() {
#Override
public void run(String s) throws Exception {
Log.w("SimpleSignalR", s);
}
}).onError(new ErrorCallback() {
#Override
public void onError(Throwable throwable) {
Log.e("SimpleSignalR", throwable.toString());
}
});
And here is the screenshot:
This work for me : Full source Android (Client) & Server GitHub
Server Slide If one argument must use this interface SubscriptionHandler1 if two argument must use this interfaceSubscriptionHandler2 ,...
Sample for two argument like :
Server slide :
using Microsoft.AspNet.SignalR;
namespace SignalRChat
{
public class ChatHub : Hub
{
public void Send(string name, string message)
{
// Two argument must use this interfaceSubscriptionHandler2 .
Clients.All.broadcastMessage(name, message);
}
}
}
Client slide :
mHubProxy.on(CLIENT_METHOD_BROADAST_MESSAGE,
new SubscriptionHandler2<String, String>() {
#Override
public void run(final String name,final String msg) {
final String finalMsg = msg.toString();
// display Toast message
mHandler.post(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(), finalMsg, Toast.LENGTH_SHORT).show();
}
});
}
}
, String.class,String.class);
For catch all message can use this :
mHubConnection.received(new MessageReceivedHandler() {
#Override
public void onMessageReceived(final JsonElement json) {
Log.e("onMessageReceived ", json.toString());
mHandler.post(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(), json.toString(), Toast.LENGTH_SHORT).show();
}
});
}
});
The SignalR team recently released a Java client for ASP.NET Core SignalR. Here is a link to getting started docs https://learn.microsoft.com/en-us/aspnet/core/signalr/java-client?view=aspnetcore-2.2
do this tutorial step by step :
https://learn.microsoft.com/en-us/aspnet/core/tutorials/signalr?tabs=visual-studio-mac&view=aspnetcore-5.0
1.According above tutorial publish your chat server to favorite host
2.add this dependency to your android sample:
implementation 'com.microsoft.signalr:signalr:3.0.0'
3.add these permission to manifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
4.below code is MainActivity.class:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
HubConnection hubConnection =
HubConnectionBuilder.create("https://your_chat_server_url/chatHub").build();
TextView textView = (TextView)findViewById(R.id.tvMain);
ListView listView = (ListView)findViewById(R.id.lvMessages);
Button sendButton = (Button)findViewById(R.id.bSend);
EditText editText = (EditText)findViewById(R.id.etMessageText);
List<String> messageList = new ArrayList<String>();
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(MainActivity.this,
android.R.layout.simple_list_item_1, messageList);
listView.setAdapter(arrayAdapter);
hubConnection.on("ReceiveMessage", (user, message)-> {
runOnUiThread(new Runnable() {
#Override
public void run() {
arrayAdapter.add( user + " : " + message);
arrayAdapter.notifyDataSetChanged();
}
});
}, String.class,String.class);
sendButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String message = editText.getText().toString();
String user = "SAEID";
editText.setText("");
try {
hubConnection.send("SendMessage", user,message);
} catch (Exception e) {
e.printStackTrace();
}
}
});
new HubConnectionTask().execute(hubConnection);
}
static class HubConnectionTask extends AsyncTask<HubConnection, Void, Void>{
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected Void doInBackground(HubConnection... hubConnections) {
HubConnection hubConnection = hubConnections[0];
hubConnection.start().blockingAwait();
return null;
}
}
}
5.below code is activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="vertical" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/tvMain" />
<ListView
android:layout_height="0dp"
android:layout_weight="1"
android:layout_width="fill_parent"
android:id="#+id/lvMessages"
android:transcriptMode="alwaysScroll">
</ListView>
<EditText
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:id="#+id/etMessageText"
android:hint="Enter Message" />
<Button
android:text="Send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/bSend" />
</LinearLayout>
For those who are implementing signalR client in android and the given answer here doesn't help in receiving the messages can check out this answer by rejnev.
The answer implements a different method connection.received() which is able to receive message callbacks from the server in my case.

Private chat with SignalR - talk to strangers

I would like to write a simple chat on the principle as omegle.com. I wrote that if the user enters the server and the queue is empty creates a new group and falls to the queue. When the other person enters, it connects with this in the queue.
Here's my code:
public class UserGroup
{
public string GroupName { get; set; }
}
public class ChatHub : Hub
{
public static Queue<UserGroup> Users = new Queue<UserGroup>();
public static string Group { get; set; }
public Task JoinGroup(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
public override System.Threading.Tasks.Task OnConnected()
{
if(Users.Count == 0)
{
var user = new UserGroup { GroupName = Context.ConnectionId };
Users.Enqueue(user);
Group = user.GroupName;
JoinGroup(user.GroupName);
}
else
{
JoinGroup(Users.Peek().GroupName);
Group = Users.Peek().GroupName;
Users.Dequeue();
}
return base.OnConnected();
}
public void SayHello(string name, string helloMsg)
{
Clients.Caller.Hello(name, helloMsg);
}
public void Send(string msg)
{
Clients.Group(Group).SendMessage(msg);
}
}
Unfortunately, when I connect to someone else, everything breaks down and does not create a new group for new people. All static data, but unfortunately not SignalR allows otherwise. You have an idea how to get around this?
I don't think you need a Queue or use groups in this scenario. The easiest solution would be to send the stranger's client id to the other client. You should incorporate locking because you access shared state from different hub instances:
Create a new ASP.NET Web Application project and select MVC as framework.
Add Microsoft.AspNet.SignalR and knockoutjs nuget packages and update all packages.
The current SignalR version requires that you add a Startup class:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
Add a hub class:
public class ChatHub : Hub
{
private static string waitingUser;
private static readonly object SyncLock = new object();
public void SendMessage(string text, string clientId)
{
this.Clients.Client(clientId).addMessage(text);
}
public override Task OnConnected()
{
var newUser = this.Context.ConnectionId;
string otherUser;
lock (SyncLock)
{
if (waitingUser == null)
{
waitingUser = newUser;
return base.OnConnected();
}
otherUser = waitingUser;
waitingUser = null;
}
this.Clients.Caller.startChat(otherUser);
this.Clients.Client(otherUser).startChat(newUser);
return base.OnConnected();
}
}
Finally, add the following code to your view:
#{
ViewBag.Title = "Chat wit a Stranger";
}
<h1>Chat with a Stranger</h1>
<div data-bind="foreach: messages">
<div data-bind="text: $data"></div>
</div>
<hr/>
<form data-bind="submit: send, visible: connected">
<input type="text" data-bind="value: text" />
<button type="submit">Send</button>
</form>
<script src="~/Scripts/knockout-3.2.0.debug.js"></script>
<script src="~/Scripts/jquery-2.1.1.js"></script>
<script src="~/Scripts/jquery.signalR-2.1.2.js"></script>
<script src="~/signalr/hubs"></script>
<script>
var hub = $.connection.chatHub;
var vm = {
otherUser: "",
messages: ko.observableArray(["Waiting for a stranger..."]),
connected: ko.observable(false),
text: ko.observable(""),
send: function () {
var text = vm.text();
if (text.length == 0) return;
hub.server.sendMessage(text, vm.otherUser);
vm.messages.push("You: " + text);
vm.text("");
},
addMessage: function(text) {
vm.messages.push("Stranger: " + text);
},
startChat: function (otherUser) {
vm.otherUser = otherUser;
vm.messages(["A stranger has connected. Say hello!"]);
vm.connected(true);
}
}
ko.applyBindings(vm);
hub.client.startChat = vm.startChat;
hub.client.addMessage = vm.addMessage;
$.connection.hub.start();
</script>

Categories