I try to do list of usb devices, connected by serial odt with smartphone, within xamarin.forms.
To do that I use this project https://github.com/anotherlab/UsbSerialForAndroid
How to do listview in shared project with devices from Project.Droid.MainActivity? I tried to do that with dependency service:
This is my Page1(where I want to have listview):
public partial class Page1 : ContentPage {
public Page1()
{
InitializeComponent();
DependencyService.Get<Interface1>().moj();
}
}
My interface:
namespace SensDxMobileApp.Views.MainWindow {
public interface Interface1 {
void moj();
}
}
And MyActivity(Droid project):
[assembly: Xamarin.Forms.Dependency(typeofProject.Droid.MainActivity))]
namespace Project.Droid {
public class MainActivity: Interface
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
listView = new Android.Widget.ListView;
}
public async void moj()
{
adapter = new UsbSerialPortAdapter(this);
listview.Adapter = adapter;
listView.ItemClick += async (sender, e) =>
{
await OnItemClick(sender, e);
};
await PopulateListAsync();
detachedReceiver = new UsbDeviceDetachedReceiver(this);
RegisterReceiver(detachedReceiver, new IntentFilter(UsbManager.ActionUsbDeviceDetached));
}
}
But I have an error "Object reference not set to an instance of an object.", on " DependencyService.Get().moj()" in Page1();
Did someone do something similar? Thanks
If you going the DependencyService route, then you'll want to create a separate class that implements Interface1 and registering that as a dependency service. I don't think you can register the MainActivity as a DependencyService implementation.
One problem that you are going to hit this is mostly async code and callbacks.
You also shouldn't be newing up an Android ListView as a DependencyService call. That would be better suited as a custom renderer. As a DependencyService implementation, you would want the moj() method to return data that can be consumed by the Xamarin.Forms code. You would need more than just that method. You would need code to initialize the UsbSerialPort class, code to query the list of devices, and then invoke a callback that sends back that list, In theory anyway. I never tested that library with Forms.
Related
In capacitor 3, I've build a minimal plugin in Android which can send events from the native Android to javascript, whenever something is happening, by listening for events in javascript.
It's working fine from within the class, i.e. overriding OnStart of the class. But when I want to call the class and notify listeners from another class, how do I call the plugin class without making a new instance, so that whoever is listening, can still get the event.
I tried implementing it as singleton, but it is not allowed, I get a error in javascript error handler when doing so, just when I start listening to events. Also I can't make it static.
So how do I call the plugin class without making a new instance?
The javascriptcode:
IonicNativePlugin.addListener('EVENT_LISTENER_NAME', (message) => {
alert(message.message);
}).catch((error) => {
alert('Error in EventListener');
});
The java MainActivity code:
public class MainActivity extends BridgeActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
registerPlugin(IonicNativeLogisPlugin.class);
}
}
The plugin java code:
#CapacitorPlugin(name = "IonicNativePlugin")
public class IonicNativePlugin extends Plugin {
#PluginMethod
public void NotifyListeners(PluginCall call){
JSObject result = new JSObject();
result.put("message", "Hello!");
notifyListeners("EVENT_LISTENER_NAME", result);
}
public void TheMethodIWantToCall(){
JSObject ret = new JSObject();
ret.put("message", "Hello from LISTENER!!");
notifyListeners("EVENT_LISTENER_NAME", ret);
}
}
The class I like to call from:
public class NotificationServiceExtension implements OSRemoteNotificationReceivedHandler {
#Override
public void remoteNotificationReceived(Context context, OSNotificationReceivedEvent notificationReceivedEvent) {
OSNotification notification = notificationReceivedEvent.getNotification();
notificationReceivedEvent.complete(notification);
<-- call should be here
}
}
I recently started learning Xamarin and was following through some tutorials that show how to use Xamarin.Firebase.Auth nuget package to handle authentication. I was able to login and get the Id Token at that point without a problem.
I also want to use an api that I have built that uses the token for it's JWT. I've used Firebase before and know that their id tokens expire after one hour. So I looked around for a tutorial on how to setup my code to handle the token changes, but couldn't find anything. I started going through the code and the only thing I could find was a method to add an Id Token Listener. So, I put together a Listener class.
public class IdTokenListener : Java.Lang.Object, FirebaseAuth.IIdTokenListener
{
public EventHandler<TokenChangedEventArgs> IdTokenChanged;
public class TokenChangedEventArgs: EventArgs
{
public string Token { get; set; }
}
public void OnIdTokenChanged(FirebaseAuth auth)
{
auth.CurrentUser.GetIdToken(false).AsAsync<GetTokenResult>().ContinueWith((task) =>
{
IdTokenChanged?.Invoke(this, new TokenChangedEventArgs { Token = task.Result.Token });
});
}
}
Then in the MainActivity.cs file of the Android project (haven't worked on the iOS project yet), I created a class level variable to hold an instance of the listener. I also called the AddIdTokenListener method as well as adding an event handler within the OnCreate method:
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
IdTokenListener idTokenListener = new IdTokenListener();
protected override void OnCreate(Bundle savedInstanceState)
{
// Other code removed for space
FirebaseAuth.Instance.AddIdTokenListener(idTokenListener);
idTokenListener.IdTokenChanged += OnIdTokenChanged;
LoadApplication(new App());
}
private void OnIdTokenChanged(object sender, IdTokenListener.TokenChangedEventArgs e)
{
Console.WriteLine("Main Activity - OnIdTokenChanged: " + e.Token);
}
}
When the app loads or I use the login page, the token listener is called and I get the expected results. However, if I leave the app running, the token listener is never called again. I even left it running overnight just to make sure it wasn't a timing issue. Is there something else I need to do in order to get this to work?
I have an application which displays data (posts) from a web API.
A background service syncs this data at some unknown time and saves it.
When visiting my main activity it loads this data and displays it in a RecyclerView
The loading is handled via a singleton class
I currently test the main activity as follows
#Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);
#Test
public void testDataLoad() {
int postsTotal = DataSingleton.getInstance().getPostsCount();
ViewInteraction empty = onView(withId(R.id.empty_view));
ViewInteraction recycler = onView(withId(R.id.recycler_view));
if (postsTotal == 0) {
empty.check(matches(isDisplayed()));
recycler.check(matches(not(isDisplayed())));
} else {
empty.check(matches(not(isDisplayed())));
recycler.check(matches(isDisplayed()));
recycler.check(new RecyclerViewItemCountAssertion(greaterThan(postsTotal)));
}
}
I know that this can't be the right way to write tests. I want to be able to test both with an empty data set and a non-empty set so that the if-else is two separate tests. The only way I think I can achieve it is to mock the data.
Is there another way?
Can I use Mockito to make the MainActivity use mock data without modifying the production code? Is my only choice to make it inject either real or mocked data providers in place of my singleton?
Is it better to just uninstall and reinstall my app each time so there is no data to start with and then continue with real data testing?
Android Activity are heavyweight and hard to test. Because we don't have control over the constructor, it is hard to swap in test doubles.
The first thing to do is to make sure you are depending on an abstraction of the data-source rather than a concretion. So if you are using a singleton with a getPostsCount() method then extract an interface:
interface DataSourceAbstraction {
int getPostsCount();
}
Make a wrapper class that implements your interface:
class ConcreteDataSource implements DataSourceAbstraction {
#Override
int getPostsCount() {
return DataSingleton.getInstance().getPostsCount();
}
}
And make the Activity depend on that rather than the concrete DataSingleton
DataSourceAbstraction dataSourceAbstraction;
#Override
protected void onCreate(Bundle savedInstanceState) {
super(savedInstanceState);
injectMembers();
}
#VisibleForTesting
void injectMembers() {
dataSourceAbstraction = new ConcreteDataSource();
}
You can now swap in a test double by subclassing and overriding injectMembers that has relaxed visibility. It's a bad idea do this in enterprise development, but there are less options in Android Activities where you don't control the constructor of the class.
You can now write:
DataSourceAbstraction dataSource;
//system under test
MainActivity mainActivity
#Before
public void setUp() {
mockDataSource = Mockito.mock(DataSourceAbstraction.class);
mainActivity = new MainActivity() {
#Override
void injectMembers() {
dataSourceAbstraction = mockDataSource;
}
};
}
I'm start learning RxJava and I like it so far. I have a fragment that communicate with an activity on button click (to replace the current fragment with a new fragment). Google recommends interface for fragments to communicate up to the activity but it's too verbose, I tried to use broadcast receiver which works generally but it had drawbacks.
Since I'm learning RxJava I wonder if it's a good option to communicate from fragments to activities (or fragment to fragment)?. If so, whats the best way to use RxJava for this type of communication?. Do I need to make event bus like this one and if that's the case should I make a single instance of the bus and use it globally (with subjects)?
Yes and it's pretty amazing after you learn how to do it. Consider the following singleton class:
public class UsernameModel {
private static UsernameModel instance;
private PublishSubject<String> subject = PublishSubject.create();
public static UsernameModel instanceOf() {
if (instance == null) {
instance = new UsernameModel();
}
return instance;
}
/**
* Pass a String down to event listeners.
*/
public void setString(String string) {
subject.onNext(string);
}
/**
* Subscribe to this Observable. On event, do something e.g. replace a fragment
*/
public Observable<String> getStringObservable() {
return subject;
}
}
In your Activity be ready to receive events (e.g. have it in the onCreate):
UsernameModel usernameModel = UsernameModel.instanceOf();
//be sure to unsubscribe somewhere when activity is "dying" e.g. onDestroy
subscription = usernameModel.getStringObservable()
.subscribe(s -> {
// Do on new string event e.g. replace fragment here
}, throwable -> {
// Normally no error will happen here based on this example.
});
In you Fragment pass down the event when it occurs:
UsernameModel.instanceOf().setString("Nick");
Your activity then will do something.
Tip 1: Change the String with any object type you like.
Tip 2: It works also great if you have Dependency injection.
Update:
I wrote a more lengthy article
Currently I think my preferred approach to this question is this to:
1.) Instead of one global bus that handles everything throughout the app (and consequently gets quite unwieldy) use "local" buses for clearly defined purposes and only plug them in where you need them.
For example you might have:
One bus for sending data between your Activitys and your ApiService.
One bus for communicating between several Fragments in an Activity.
One bus that sends the currently selected app theme color to all Activitys so that they can tint all icons accordingly.
2.) Use Dagger (or maybe AndroidAnnotations if you prefer that) to make the wiring-everything-together a bit less painful (and to also avoid lots of static instances). This also makes it easier to, e. g. have a single component that deals only with storing and reading the login status in the SharedPreferences - this component could then also be wired directly to your ApiService to provide the session token for all requests.
3.) Feel free to use Subjects internally but "cast" them to Observable before handing them out to the public by calling return subject.asObservable(). This prevents other classes from pushing values into the Subject where they shouldn't be allowed to.
Define events
public class Trigger {
public Trigger() {
}
public static class Increment {
}
public static class Decrement {
}
public static class Reset {
}
}
Event controller
public class RxTrigger {
private PublishSubject<Object> mRxTrigger = PublishSubject.create();
public RxTrigger() {
// required
}
public void send(Object o) {
mRxTrigger.onNext(o);
}
public Observable<Object> toObservable() {
return mRxTrigger;
}
// check for available events
public boolean hasObservers() {
return mRxTrigger.hasObservers();
}
}
Application.class
public class App extends Application {
private RxTrigger rxTrigger;
public App getApp() {
return (App) getApplicationContext();
}
#Override
public void onCreate() {
super.onCreate();
rxTrigger = new RxTrigger();
}
public RxTrigger reactiveTrigger() {
return rxTrigger;
}
}
Register event listener wherever required
MyApplication mApp = (App) getApplicationContext();
mApp
.reactiveTrigger() // singleton object of trigger
.toObservable()
.subscribeOn(Schedulers.io()) // push to io thread
.observeOn(AndroidSchedulers.mainThread()) // listen calls on main thread
.subscribe(object -> { //receive events here
if (object instanceof Trigger.Increment) {
fabCounter.setText(String.valueOf(Integer.parseInt(fabCounter.getText().toString()) + 1));
} else if (object instanceof Trigger.Decrement) {
if (Integer.parseInt(fabCounter.getText().toString()) != 0)
fabCounter.setText(String.valueOf(Integer.parseInt(fabCounter.getText().toString()) - 1));
} else if (object instanceof Trigger.Reset) {
fabCounter.setText("0");
}
});
Send/Fire event
MyApplication mApp = (App) getApplicationContext();
//increment
mApp
.reactiveTrigger()
.send(new Trigger.Increment());
//decrement
mApp
.reactiveTrigger()
.send(new Trigger.Decrement());
Full implementation for above library with example -> RxTrigger
I have a weird / unique situation with my ListView. This is the scenario:
I'm making use of the MVP design pattern. As the Activity starts, it raises an event to notify the presenter to fetch some data from a web service. The web service call is an Async call. Once the web service Completed event is raised, I take the result and push it into a property (which is of type Array) that resides within my View / Activity.
Everything I mentioned works just fine, but as soon as the device is rotated, some interesting developments take place.
The async call resumes as normal and provides the property (Array) with a value. So nothing wrong there... (And yes there is data in the collection) I then set the ListView Adapter and call the notifyDataSetChanged, but nothing happens. The UI is not updated or anything?? If I re-enter the Activity the data is visible again ??
I even tried calling invalidateViews and invalidate on the ListView - this didn't do anything.
Could someone please assist me in this matter?
Many thanks in advance!
[Update]
I would like to stress the fact that I am making use of C# (Xamarin) and not Java (:sigh: - yes I know). Furthermore, I am not making use of the ASyncTask class, instead I'm making use of the async methods created within the proxy classes generated by Visual Studio. Pretty straight forward, but this is the code that populates the ListView - the property is set from the presenter
Presenter
Where View is of type IContactsView
protected override void OnCollectData(System.Collections.IEnumerable data, Type typeOfData)
{
if (data != null && typeOfData != null && typeOfData.Equals(typeof(UserContact)))
{
this.View.UserInformationCollection = data.Cast<UserContact>().ToArray();
}
}
Activity
The activity implements IContactsView
public UserContact[] UserInformationCollection
{
get
{
return this._userInformationCollection;
}
set
{
this.RunOnUiThread(() =>
{
this._userInformationCollection = value;
ListView listview = this.FindViewById<ListView>(Resource.Id.userLV);
if (listview != null)
{
UserContact[] subsidiesList = this.GetIndexedContacts(this._userInformationCollection);
listview.Adapter = new ContactsAdapter(this, subsidiesList.ToList());
((ContactsAdapter)listview.Adapter).NotifyDataSetChanged();
}
});
}
}
[/Update]
Found a much better solution! So please ignore the static variable idea!
Activity:
Override the OnRetainNonConfigurationInstance and return the presenter
public override Java.Lang.Object OnRetainNonConfigurationInstance()
{
return this._presenter;
}
Within the OnCreate check the LastNonConfigurationInstance and get the presenter - if it isn't null:
protected override void OnCreate(Bundle bundle)
{
...
if (this.LastNonConfigurationInstance != null)
{
this._presenter = this.LastNonConfigurationInstance as ContactsPresenter;
this._presenter.RefreshView(this);
}
else
{
// create a new presenter
this._presenter = new ContactsPresenter(this);
}
...
}
So maybe, you saw what I did in the previous code sample? Yes, I send the new instance of the activity to the presenter - have a look at the RefreshView
Presenter:
So within my base presenter I have the following method:
public class Presenter<T> : Java.Lang.Object, IPresenter where T : IView
{
/// <param name="view">The view.</param>
public void RefreshView(T view)
{
this.View = view;
}
}
The above code helps my presenter say with the creation of new activities - so when it returns data after the async call it will have the latest and greatest instance of the activity!
Hope this helps!
Kind regards,
Got it working by doing the following:
declare a static variable of the activity:
private static ContactsActivity _cachedActivity = null;
Overrode the OnResume within the activity and set the variable:
protected override void OnResume()
{
base.OnResume();
_cachedActivity = this;
}
Override the OnCreate within the activity and set the variable:
protected override void OnCreate(Bundle bundle)
{
...
_cachedActivity = this;
...
}
Lastly I changed the property mentioned earlier:
public USBUserContact[] UserInformationCollection
{
get
{
return this._userInformationCollection;
}
set
{
_cachedActivity.RunOnUiThread(() =>
{
_cachedActivity._userInformationCollection = value;
ListView listview = _cachedActivity.FindViewById<ListView>(Resource.Id.userLV);
if (listview != null)
{
UserContact[] subsidiesList = _cachedActivity.GetIndexedContacts(_cachedActivity._userInformationCollection);
listview.Adapter = new ContactsAdapter(_cachedActivity, subsidiesList.ToList());
((ContactsAdapter)listview.Adapter).NotifyDataSetChanged();
}
});
}
}
Kind regards,