HackerNews - Windows Phone App

HackerNews - Windows Phone App

Following the example of the VBM on Android series; I'm going to develop a HackerNews Reader for the Windows phone/UWP.
This should be interesting as I've never done a UWP app before. This will be my first foray into it!

SO EXCITED!!!

Getting Started

Visual Studio 2017 is fresh; Jetbrains Resharper Ultimate is installed - Now I can work.

Let's go through the entire flow - Mostly 'cause I have no idea what I'm doing.

Create The Project

I like the new Template search
. You do have to know what you're going for, but it can speed up getting started with known projects. Otherwise, it's a 1/2 dozen clicks or so.
Since this is a Universal Windows Platform app; let's search for Universal:

I like how Unit Test is first; but - NO! Let's do a Blank App first.

This pops up the normal dialog for project creation; with the desired template already selected

Enter a name, select a folder and Create... I mean OK.

Apparently, though it makes sense, need to select the minimum version as well as the targeted version. Since there's only 3... I'll span the entire range.

Until I hit a point supporting an older version is annoying; then I'll up it.

Project Created.

Since I have mostly no idea what I'm doing in a UWP perspective; I'm gonna do what I did for the Android version (which, yes, is still in progress) - Start at the network layer. I need to figure out how to mock/fake it.

Because I really like how Retrofit works; I've found a version for UWP RestEase.

Now that I've found it... how to include it... I'm assuming NuGet... Let's try:

Yep; it's there. Makes this very easy.

Except it's giving me an error...

Time to interwebs and see if I can find something...

But couldn't get RestEase working. If I knew more about UWP and.NET Core; maybe. Until then; gonna try the project RestEase is based on; Refit which is based on Retrofit.
It installed via NuGet just fine. Wooo!

Before I can write any code to implement this network layer... TEST PROJECT!
I'm not walking through adding another project. If there's too little experience with Visual Studio to pull it off; ehhh - The internet can help.

This should be interesting because it's been a while for heavy VS testing, and I don't know the details of this network library. Just expectations of it being like Retrofit.

Initial Testing

With the initial test in place; I putzed about a bit getting exactly what I needed in place.

I don't start with the API interface. I start with the Network class. In this case; like Android; it's named HackerNewsNetwork; which required the creation of the HackerNewsNetworkTests class.
Starting with a simple failing test

[TestMethod]
public void Exists()
{
    new HackerNewsNetwork();
}

It fails because it doesn't exist. Resharper up the new class; extract it. Drag it into the actual project (I don't know the keys for that). Things pass!

public class HackerNewsNetwork{}

Resharper isn't running the tests right now; it's ... confused? about the UWP test app. Not sure; got it running via VS's runner.

Next, I need to get a response from something from the HackerNewsNetwork.
Start off with something simple to build out the desired method

public void ShouldReturnTaskStories(){
    new HackerNewsNetwork().TopStories();
}

Failing for lack of the method.

public class HackerNewsNetwork
{
    public void TopStories()
    {
    }
}

And easily passed!

That's not the test we want; so let's modify it to actually get something back

public void ShouldReturnTaskStories(){
    var result = new HackerNewsNetwork().TopStories();
}
public class HackerNewsNetwork
{
    public Task<string> TopStories()
    {
        return null;
    }
}

As we can see; we step through and modify the functionality. We'll expect a result. And we're going to go a little on the "obvious implementation" side of things, and get the production code more general without the critical mass of tests forcing the design; we know where we are going. TDD helps us get there, but we know the architecture we're going for.

The resulting tests look like


    [TestClass]
    public class HackerNewsNetworkTests
    {
        private const string HostUrl = "http://blog.quantityandconversion.com";

        [TestMethod]
        public void Exists()
        {
            new HackerNewsNetwork();
        }
        
        [TestMethod]
        public void ShouldReturnTaskStories()
        {
            var fakeResponseHandler = new FakeResponseHandler();
            fakeResponseHandler.AddFakeResponse(new Uri($"{HostUrl}/topstories.json"), new HttpResponseMessage(HttpStatusCode.OK){Content = new StringContent("You Got It")});
            var taskStories = new HackerNewsNetwork(fakeResponseHandler).TopStories();
            var actual = taskStories.Result;
            actual.Should().Be("You Got It");
        }
    }

and the HackerNewsNetwork class is


public class HackerNewsNetwork
{
    private const string HostUrl = "http://blog.quantityandconversion.com";
    private static HttpMessageHandler _messageHandler = null;

    public HackerNewsNetwork() { }

    public HackerNewsNetwork(HttpMessageHandler messageHandler)
    {
        _messageHandler = messageHandler;
    }


    public Task<string> TopStories()
    {
        var hackerNewsApi = RestService.For<IHackerNewsApi>(HostUrl,
            new RefitSettings() { HttpMessageHandlerFactory = () => _messageHandler });
        return hackerNewsApi.TopStories();
    }
}

and the Refit interface is

    public interface IHackerNewsApi
    {
        [Get("/topstories.json")]
        Task<string> TopStories();
    }

This isn't complete yet. It's just returning a string. That was to get the network fake in place. Now that we have this; we can TDD the entire codebase and never need to hit the actual data source. YAY! We can live in our own little dream work w/o any development time dependencies on the server. AKA - I can be lazy. Sure; it's already in place, but I gotta be prepared for the dark times.

I've been the server guy blocking client work, and I've been on a poorly written client blocked by the server. I try to ensure the best of both worlds - Not being blocked.

As you can see I have the 'test' constructor for dependency injection. I'm still waiting for a better method to do this.
I'm not going to force "but it could be used for X" onto the system. I'll hold that this is a boundary layer, testing needs access; it's either a constructor or an exposed property. Until shown better; of course.

Get the test correct

To get the TopStories testing correctly we need to introduce the JSON parsing components. I'm not sure what this will look like in this yet. It'll require a little exploration to find the format.

Changing Gears

A side project came up to do a UWP app; not the Hacker News. It supports very similar functionality; just not HackerNews.
I started pushing a bit more on the Refit networking; it's lacking. It's fantastic - Just lacking compared to what Retrofit has to offer; but... Yeah; most things will.

I'm working on reproducing some of the extended functionality in a basic form. As the app requires more; I'll extend more. I'll be contributing these changes back to Refit when I get them in a good enough state.

My biggest work right now is basically recreating the Response from Retrofit. I really like the dual purpose "error" and "success" object. I think with C#'s async/await functionality; I'll be able to get away not needing the CallBack. I don't see a need for the Call class yet, but it's on my radar.

My current Response object is very simple


    public class Response
    {
        private HttpStatusCode _statusCode;
        private string _content;

        public Response(HttpResponseMessage rawHttpResponseMessage, ApiException apiException)
        {
            if (rawHttpResponseMessage != null)
            {
                ParseHttpResponseMessage(rawHttpResponseMessage);
            }
            else if (apiException != null)
            {
                ParseApiException(apiException);
            }
            else
            {
                throw new NullReferenceException("raw Response is null");
            }
        }

        private void ParseApiException(ApiException apiException)
        {
            _statusCode = apiException.StatusCode;
            _content = apiException.Content;
        }

        private void ParseHttpResponseMessage(HttpResponseMessage rawHttpResponseMessage)
        {
            _statusCode = rawHttpResponseMessage.StatusCode;
            _content = rawHttpResponseMessage.Content?.ReadAsStringAsync().Result;
        }

        public HttpStatusCode StatusCode()
        {
            return _statusCode;
        }

        public string Message()
        {
            return _content;
        }

        public MemberInfoJson Body()
        {
            return _content == null ? null : JsonConvert.DeserializeObject<MemberInfoJson>(_content);
        }
    }

With some cleanup in it's future. I did this in an hour or so while my daughter did homework. TDD'd; but not paired. It shows it's lack of pairing. There's "it worked" in the code.

I need to explore and build up some more. It's showing it's cruft.


It's been a few weeks since I last worked on this.
I know the Response class is a bit of cruft; too much time trying to optimize will take away and perhaps inhibit future emergent design. We'll run with it and improve it when we come across a need to improve.

I've been working on switching over to the NetworkMemberAccess#MemberInfo2 method; mostly by deleting MemberInfo and renaming to that. Fixing the red and failing tests.
It only made 3 tests fail, and those are resolved by changing the expected string value from whatever I entered into the member id.
A minor update to the tests, but actually a substantial update to the code. No longer returning a primitive and a few minor changes to a few failing tests.
I enjoy the freedom having a solid test suite gives.

Since I'm back to ... huh... I just realized; I'm not in the project for this app. :X

This is a working app... Well... After checking the HackerNews UWP; it looks like the app for work has a little more progress in the networking layer...

I'll have to just pull it into the HN app. Which has me C&Ping code across projects. This demonstrates a commonality... almost like I should create a shareable form... Hmmm..

Later. I want to work on just one app for now; gonna go with the UWP app; mostly so I can blog about it.
One thing the C&P does force; a smidge earlier than expected; and not entirely TDD'd - Generics. Currently, it was using a MemberInfoJson object; this doesn't exist. I don't have a class to replace it with... I guess I'll throw in the Item class.

Ooooo.... This will be interesting. I know I'm going to need Item and not Story based classes. How's this going to change the evolution of the code? I'm looking forward to seeing that!

The ItemJson class being added to enable compiling, which is required for going Green is being added. It's sorta TDD'd... I had to do it to be green!

Anyway; UWP HackerNews is compiling; I'm not sure what I was doing... I'm a bit mixed up with the work app.

I was looking to get this post to a solid stopping point... I think having the foundation of the networking in place is a solid place to stop.

I will next work on the Network Access component; which in the Android app masks the callback invocation.
There are some aspects from RetroFit I'm not seeing in Refit; so I'll have to dig a bit and see if I'm missing how the feature is implemented; or if I need to do some custom extensions.

Until I get's the more info; I think I'll call this good for now. Code

Show Comments