Category Archives: Uncategorized

JonikaBot – En liten uppdatering

Nu har jag lagt till några fler funktioner till min bot.

  • Ny hashtag #jonikabotfbkvit om man vill ha en svart puck med vitt FBK-emblem på sin profilbild

  • Nu kan man även bifoga en bild i sin tweet om man vill ha ett emblem på den istället för sin nuvarande profilbild.

  • Några små justeringar av positionen av emblemen, så att inga delar ska klippas bort när Twitter rundar bilden.

Har ni fler idéer om funktioner eller tycker någon funktion måste ändras så säg gärna till, jag är öppen för det mesta 🙂

Xamarin Droid: Putting LocationManager in a separate class

I almost always put all logic in som sort of separate classes in my projects. I simply think it looks better, is way more easy to understand and the better way.

When using Xamarin to create cross-plattform apps, you probably use dependency injection to minimize ugly code blocks like this.

if (Device.OS == TargetPlatform.iOS) {
}else{
}

That also means you pretty much need to create interfaces that you can use for all plattforms, which leads to this topic: You need to put logic in classes.

I had a bit of a struggle to implement location services on Android this way, hence this post. Most bloggs and articles i found used the main activity. For iOS it was very straight forward, so I’m leaving that part out.

And to be clear: This is my POC-code, not intended to copy and paste into production!

First of all, i had to create an interface i could use for iOS and Droid. I kept it simple, and only included stuff i needed: The current location, a method to initialize everything and a event to trigger when position changes.

public interface ILocationManager
{
	Tuple<double, double> GetCurrentLocation();
	void StartLocationUpdates();
	event EventHandler LocationUpdated;
}

I also created i class holding the main activity context. I needed this to be able to use the context from my class.

public class Config {
	public static MainActivity context = null;
}

In my MainActivity i set the context of the class above:

Config.context = this;

From my cross-plattform code i need to get a instance of ILocationManager for the current plattform. For this, i use the DependencyService. I Also call my StartLocationUpdates method to start getting location updates:

private ILocationManager LocManager = DependencyService.Get<ILocationManager> ();
LocManager.StartLocationUpdates ();

So far, all code except the config class is the same for both iOS and Android. But this is where i hit the wall on Android. I spent days trying to figure out how to create a class using my interface that would work in the same way my iOS code did. This is my solution

First, make sure to put this line above the namespace in your class to get dependency injection to work:

[assembly: Xamarin.Forms.Dependency (typeof(NautiCalc.Droid.LocationManager))]

The second thing i found important was to decorate your class with the Activity attribute. This makes sure your activity is inserted into the Manifest, which is important. You also need to inherit Activity class, and implement your interface.

[Activity]
public class LocationManager : Activity, NautiCalc.ILocationManager, ILocationListener

The rest is kind of straight forward. You need to implement a few methods because of the inheritance of Activity, and of course your own interface.

Here is my complete code:

using System;
using Android.Content;
using Android.App;
using Android.Locations;
using Android.OS;
using Android.Widget;

[assembly: Xamarin.Forms.Dependency (typeof(NautiCalc.Droid.LocationManager))]
namespace NautiCalc.Droid
{
	[Activity]
	public class LocationManager : Activity, NautiCalc.ILocationManager, ILocationListener
	{
		// event for the location changing
		public event EventHandler LocationUpdated = delegate {};

		protected Android.Locations.LocationManager locMgr;

		public Android.Locations.LocationManager LocMgr {
			get { return this.locMgr; }
		}

		public Tuple<double, double> GetCurrentLocation ()
		{
			Location lastKnown = locMgr.GetLastKnownLocation (Android.Locations.LocationManager.NetworkProvider);
			if (lastKnown != null) {
				return Tuple.Create (lastKnown.Latitude, lastKnown.Longitude);
			}
			return null;
		}

		public void StartLocationUpdates ()
		{
			locMgr = (Android.Locations.LocationManager)Config.context.GetSystemService (Context.LocationService);

			if (locMgr.AllProviders.Contains (Android.Locations.LocationManager.GpsProvider) && locMgr.IsProviderEnabled (Android.Locations.LocationManager.GpsProvider)) {
				locMgr.RequestLocationUpdates (Android.Locations.LocationManager.GpsProvider, 2000, 1, this);
			} 
		}

		protected override void OnPause ()
		{
			base.OnPause ();
			locMgr.RemoveUpdates (this);
		}

		public void OnProviderEnabled (string provider)
		{
		}

		public void OnProviderDisabled (string provider)
		{
		}

		public void OnStatusChanged (string provider, Availability status, Bundle extras)
		{
		}

		public void OnLocationChanged (Location location)
		{
			LocationData CurrentLocation = new LocationData ();
			CurrentLocation.Altitude = location.Altitude;
			CurrentLocation.Course = (double)location.Bearing;
			CurrentLocation.Latitude = location.Latitude;
			CurrentLocation.Longitude = location.Longitude;
			CurrentLocation.Speed = (double)location.Speed;
			LocationUpdated (this, new LocationUpdatedEventArgs (CurrentLocation));
		}
	}
}

Not that hard, once you figured it out. Happy coding!

Exact searches with Lucene (and searches with special characters)

There’s off course thousands of ways to solve this issue. I decided to go old-school 🙂

So whats the problem? Well, if you really want to search for an exact value, and perhaps it contains strange characters (like formulas, algorithms), maybe it’s to short to be indexed correctly… and I’m sure there are more reasons.

What can you do? One way to address this is to construct a unique string, store it in the index and search that column instead of the column containing the clear text value.

An example, lets say you have values like A(b)-c in a column, and you really only want to find exact matches. Parentheses and hyphens makes this hard, and if you replace these you risk get other matches as well. Want you can do is pass the value to a base64-encoder, which will give you a unique string without strange characters, in this case you’ll get: QShiKS1j

This has to be done when you create your index. So now you have two columns, side-by-side, one with the actual value and one with encoded value.

So, when a user enters A(b)-c in the search field, all you have to do in base64-encode it before using it in your search query, and of course search in the encoded field rather than the clear text one.

The code to encode and decode strings:

public static string Base64Encode(string text)
{
  return text != null ? Convert.ToBase64String(Encoding.UTF8.GetBytes(text)) : "";
}
public static string Base64Decode(string text)
{
  return text != null ? Encoding.UTF8.GetString(Convert.FromBase64String(text)) : "";
}

The code to create an index

IndexWriter Writer = new IndexWriter(
  FSDirectory.Open(@"C:\temp\index"),
  new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30), 
  true, 
  IndexWriter.MaxFieldLength.LIMITED
);

Document doc = new Document();
doc.Add(new Field("value", "A(b)-c", Field.Store.YES, Field.Index.ANALYZED));
doc.Add(new Field("encodedvalue", Base64Encode("A(b)-c"), Field.Store.YES, Field.Index.ANALYZED));

Writer.AddDocument(doc);
Writer.Optimize();
Writer.Dispose();

And here’s one way to search the index

FSDirectory SearchIndex = FSDirectory.Open(@"c:\temp\index");
StandardAnalyzer Analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30);
            
IndexReader Reader = IndexReader.Open(SearchIndex, true);
IndexSearcher Searcher = new IndexSearcher(Reader);
            
QueryParser Parser = new QueryParser(Lucene.Net.Util.Version.LUCENE_30, "Alias", Analyzer);
string SpecialQueryText = string.Format("encodedvalue:{0}", Base64Encode("A(b)-c"));

Query SpecialQuery = Parser.Parse(SpecialQueryText);
TopDocs Hits = Searcher.Search(SpecialQuery, null, 1000);
Document[] Result = Hits.ScoreDocs.Select(s => Searcher.Doc(s.Doc)).ToArray<Document>();

Result = Hits.ScoreDocs.Where(x => x.Score >= 1.1F).Select(s => Searcher.Doc(s.Doc)).ToArray<Document>();

 

 

Man kanske skulle blogga?

Ja varför inte… Facebook är lite personligt och Twitter är ofta lite för “kort” för att få fram det man vill… så varför inte testa om man orkar blogga. Vem vet, det kanske är kul 🙂

Det kommer antagligen bli någon slags mellanting mellan projekt på jobbet, lite fritid samt en massa “labb”-grejer bara för att se hur/om saker fungerar.