Friday, May 18, 2018

Messaging

The last chunk that I wanted to get done before the end of the semester was Messaging. It took me a while to wrap my head around how I was going to do it, and the way that I picked was definitely inefficient but it worked so I was happy.

This would require two tables in the sql server. Messages, which held the username of the person sending the message, the username of the person receiving the message, a timestamp and the actual message. The second table is the Message_Box. This had the to_user and from_user field and an incoming field which held an integer that specified the amount of messages sent by from_user to to_user that to_user hadn't seen. Every time the message page was loaded the incoming field would be reset to 0 because all the messages from that user would be loaded. The real use of the incoming field is when two users are on their messaging pages and messaging each other. I have a method called StartMessageCheckingTask() that runs every second and checks if the incoming field for that user was higher than 0. If it was it would load the amount of incoming messages from from the bottom of the list of all messages sorted by their timestamp. Unfortunately this means that the sql server is being accessed every second while your on the messaging screen which isn't the greatest but it was good enough for what I needed at the time.

Now a user could tap on a pin and a box saying "Message" would popup. Clicking on the popup would bring you to a messaging screen where you would be able to message the user who's pin you had tapped.

Custom Rendering and New Problems

Now that I had profile pictures for users I wanted to display them when you tapped on a pin. I thought this would be easy to do but there was no options provided with pins to display pictures. After doing some research I found out that in order to customize pins you had to render them differently on different devices. Meaning you had to write separate code for iOS and Android.

What you needed to do is write a CustomRendering class in both the Android code and iOS code that extended the MapRenderer class. The MapRenderer class is called every time the Map is changed letting you manipulate how the map, but more importantly how the pins, are presented. On Android you can customize the View that is presented when tapping on a pin and have it display the users profile picture. On iOS the view presented is cut into three parts the LeftCalloutAccessoryView, the middle view, and the RightCalloutAccessoryView. I used the left accessory view to show the profile picture and the middle to show the title of the pin.

All of this was of course not done without any problems. At this point in time a lot things that were working in the past started to break. Mainly the Geolocator plugin, which meant I couldn't get locations anymore. Most of my time was spent trying to fix this. I fixed it eventually by downgrading the plugin to a lower version and manually reinstalling the dependencies again.

Profile Pictures

Now that the user system was up I could add in profile pictures. For this I added a profile page which would hold the profile picture, buttons to let you upload a new picture, and a button to go to the map. The first step was letting the user pick the picture they wanted to use from the picture gallery on their phone. For this I used the Xam.Plugin.Media plugin which provides that functionality for Android and iOS. Next I added a new Table to the sql server which would store a persons username and their profile picture in a BLOB. BLOB stands for binary large object and is used to store large chunks of data. So the way it would work is a user would tap the upload picture button select a picture using the Media plugin that would then give us the picture converted into a byte array. The data in the array and the users username would then be stored in the sql server.

Next I had to take care of how someone would look like without a profile picture. I didn't want to store a picture on the server for every person that didn't have a profile picture so I found a "no avatar" picture and stored it in the resource folder on both iOS and Android. So when somebody that hadn't uploaded a profile picture posted their location or went to their profile page the "no avatar" would be loaded from resources.

The final problem to take care of was having a uniform size for the profile pictures. For this I needed to be able to resize the pictures before storing them in the database. I tried using a plugin for it but the plugin would always maintain the aspect ratio of image meaning I would always get images with varying sizes. So instead I decided to code my own image resizer. The way this works is you write an interface in the shared code and then write two classes that extend it, one in the Android code and one in the iOS. Then when calling a method from the class you use Xamarins DependencyService which selects the correct class for you depending on what system its running on. So using this I wrote my own ImageResizer class that took an image byte array and resized it to whatever size I needed.

Login Page and User System

The next thing to focus on was setting up a user system. For this I drew up a login page and added a User table to the sql server which holds the username and password. For a user system I would need to be able to create an account, check that the username used to create doesn't exist, and, if the account already exists, check that the password is correct. All of this is done in php by polling the sql server. Once I got logging in to work I needed to make sure everything you do in the app is tied to your username. Which meant, for now, passing the username between pages and adding a username field to the Location table. Now locations stored in the server would also have the persons username attached to them. This let me add another button to the map that lets you only retrieve locations that you put down. Now that I had a user system in place I could start working on the more complicated aspects of the project.

Setting Up SQL, PHP, and Sending/Loading Locations

After displaying a map the next thing to do was to let the user send their location and be able to store it. For this I used 000webhost a hosting site that provided me with a MySQL server and PHP scripting to manage it. Using the System.Net.Http Plugin in Xamarin I wrote code that would post the latitude, longitude, and place name in JSON to a php file located on my site. The file would then convert the JSON into variables, stick them into a sql call, and execute the sql against the MySQL database to store them inside the Location table. To do this I needed a way to get the users location on iOS and Android. This functionality was provided by the Xam.Plugin.Geolocator Plugin. Also I needed a way to show a popup asking for the name of the location. For this I used Acr.UserDialogs Plugin. So after putting everything together the user would be able to tap an add button which would poll Geolocator for their location, then type in the name of their location and tap Ok, which would send and store the data in the MySQL server.

Next I needed to be able to get the locations from the server and display them on the map as pins. The process works almost the same way but backwards. You pull the data from the sql database using php encode it in JSON and send it back. The only difference would be decoding the JSON on the other side. Using the plugin Newtonsoft.Json you could decode the JSON string, but what makes the plgin convenient is that you can decode the JSON straight into an Object that will hold the data. For example I created the Object LatLonPair that would hold latitude, logitude and name variables. So using Newtonsoft you could decode a json string into an array of LatLonPairs that hold all your data. From their displaying on the map is easy. just iterate over the array and place the pins on the map.

Getting a Map

The next thing to get done was to set up displaying a Map on both iOS and Android. Android uses Google Maps and iOS uses Apple Maps. To use Google Maps you need to generate an API key and input it into the Android side of the code. You don't need to do anything special for Apple Maps. Each Map also needs to ask for permissions depending on what version you are running. To solve this I used the Plugin.Permissions plugin that provides code that takes care  of permissions for both sides of the project. At this point though I ran into problems. In order for Google Maps to run on earlier versions of Android you need to download the relevant support libraries. This is done through the NuGet Package Manager. The manager should act like any package manager for Linux, it should download and install dependencies for you depending on the package you are downloading. Unfortunately at the moment it is a buggy mess. For example if a package has a dependency that can be greater or equal to a certain version NuGet will ignore the greater than sign and spit out an error message saying you are installing something that exceeds the version number. The only way to solve this, that I found, was to write down all the dependencies and manually install them at the correct version one by one. Out of all the time I spent that week working, the largest chunk was taken up by solving this problem.

Thursday, February 8, 2018

Setting Up

I started out by finding my old Git Lab account and making a new project. Then added Karl Wurst as a Reporter to monitor my progress. After this I set up Xamarin, the framework I'm going to use to program the app. With the help of Xamarin I will be able to develop for both IOS and Android at the same time. Setting up the IOS portion of Xamarin is a little tricky though. In order to compile IOS code Xamarin needs to have access to a Mac running MacOS 10.12 or higher. Since I don't have a Mac at my disposal I'm going to have run MacOS virtually in VMware. Fortunately, I found a site which provided instructions on how to do this and a download link to the OS. After getting through that I just needed to connect the VMware running MacOS to Visual Studio running Xamarin which was really easy since it just automatically found the connection for me.