Author: Keith Beatty

Xamarin Forms MVVM example

The last time I wrote about Xamarin Forms, I was looking for an MVVM example. There were many examples, but most of them were written with the PC version of Visual Studio (VS). In my experience none of them would actually work with the XAML compiler error that I noted in my last blog entry, or various other problems.

FYI – In case you think TLDR, I just want to look at the source code, it’s here: https://github.com/b12kab/SQLiteSample

I did find this entry that did was made with VS 2017 Mac. I didn’t find a link to the code, so I went ahead and typed it in (Note: there is a link to the code – but it’s a picture. Not recommended when scanning down looking for source code). I found that it didn’t work. I eventually found the source link here.  I loaded it up and found that it compiled, but when deployed to a Android device, it gave me an error System.DllNotFoundException for a missing file /system/lib/libsqlite.so.

I did find a non-MVVM solution that actually did work here; source here. I found that the SQLite worked. This project used sqlite-net-pcl instead of SQLite.Net-PCL. Going back to the above project, I tried removing the SQLite.Net and adding in sqlite-net, but that just didn’t work – too many errors to count when I compiled it. I went back to my version of SQLiteSample that didn’t work before and removed the source code and copied in the source file from the source code zip file. Note: I created this using the default .Net 2 instead of using PCL 4.x)

I did have to make changes  where there were differences in the different SQLite’s, such as in the using statements as well as the implementations of each of them for the connection. Once this was finished, I was able to compile and run the app. Always nice to see something work 🙂

Information

Not really touched upon by the article, but something really interesting, was that one XAML file was using another local XAML file as part of it’s own makeup. For instance this XAML uses:

<local:ContactView />

Which loads in the ContactView XAML form, highly useful and certainly helps on code reuse.

UI problems

What this code didn’t have going for it was UI problems. When it was initially installed and there was no data, it would pop you into the add form. This conceptually speaking would not be the thing that one would expect when accessing an app for the first time. You couldn’t go back, as this would exit the app. Once you added an entry, you would be pushed into the contact list; making once you added an entry, you would keep on stacking add -> list -> add -> list forms. There was a button on the add screen that would take you out to the list however, it would do the same thing pushing it on the stack add (push list button) -> list -> add -> list.

Horrible.

It would also do a similar action if you pressed delete all from the list view, it would delete all of the data and push you onto the add form: list -> add -> list.

In my version of this app, I’ve fixed those UI problems, so it acts as one would expect it should act.

Another example

Perhaps not so strangely enough, I did find another MVVM example also worked; it also used sqlite-net instead of the more popular (and non-working) SQLite.Net. It is here.

I do like the use of the toolbars, but I didn’t like the package layout as much as the SQLiteSample; as well as I moved past looking for a sample onto working on my own project.

If you are looking for something that will work on your Mac, be sure to review it.

 

Advertisements

Advertising my app: Getting a larger install count – Part 4

This is the final installment of this story. Here is Part 1, Part 2, and Part 3 of this story.

I did not receive any feedback from my last email to CPIMobi, so about 10 days later, I contacted PayPal and requested a refund. They issued the refund within 8 hours, so I guess that there had been others who have had problems with them as well. They still hadn’t broken 400 installs (sigh).

Here is the final chart:

Date Impressions Clicks CTR Installations CR
6/27/18 40 1 2.50% 1 100.00%
6/26/18 23 1 4.35% 1 100.00%
6/25/18 42 1 2.38% 1 100.00%
6/24/18 27 2 7.41% 1 50.00%
6/23/18 33 3 9.09% 1 33.33%
6/22/18 23 1 4.35% 1 100.00%
6/21/18 45 1 2.22% 1 100.00%
6/20/18 55 3 5.45% 1 33.33%
6/19/18 13 1 7.69% 1 100.00%
6/18/18 25 4 16.00% 2 50.00%
6/17/18 10 1 10.00% 1 100.00%
6/16/18 9 3 33.33% 2 66.67%
6/15/18 8 1 12.50% 0 0.00%
6/14/18 108 2 1.85% 1 50.00%
6/13/18 42 3 7.14% 2 66.67%
6/12/18 21 1 4.76% 1 100.00%
6/11/18 51 3 5.88% 2 66.67%
6/10/18 47 2 4.26% 2 100.00%
6/9/18 19 1 5.26% 1 100.00%
6/8/18 8 1 12.50% 0 0.00%
6/7/18 4 4 100.00% 2 50.00%
6/6/18 2 2 100.00% 2 100.00%
6/5/18 4 3 75.00% 3 100.00%
6/4/18 10 2 20.00% 2 100.00%
6/3/18 7 4 57.14% 3 75.00%
6/2/18 18 7 38.89% 3 42.86%
6/1/18 3 1 33.33% 1 100.00%
5/31/18 2 2 100.00% 2 100.00%
5/30/18 6 1 16.67% 1 100.00%
5/29/18 18 3 16.67% 3 100.00%
5/28/18 2 1 50.00% 1 100.00%
5/27/18 11 9 81.82% 7 77.78%
5/26/18 8 8 100.00% 4 50.00%
5/25/18 2 2 100.00% 0 0.00%
5/24/18 7 6 85.71% 5 83.33%
5/23/18 5 5 100.00% 4 80.00%
5/22/18 4 4 100.00% 3 75.00%
5/21/18 4 3 75.00% 2 66.67%
5/20/18 8 8 100.00% 7 87.50%
5/19/18 8 8 100.00% 6 75.00%
5/18/18 10 10 100.00% 8 80.00%
5/17/18 10 8 80.00% 5 62.50%
5/16/18 6 5 83.33% 3 60.00%
5/15/18 10 10 100.00% 9 90.00%
5/14/18 5 4 80.00% 2 50.00%
5/13/18 18 16 88.89% 10 62.50%
5/12/18 22 22 100.00% 13 59.09%
5/11/18 13 13 100.00% 8 61.54%
5/10/18 17 17 100.00% 11 64.71%
5/9/18 40 22 55.00% 17 77.27%
5/8/18 45 43 95.56% 36 83.72%
5/7/18 64 62 96.88% 50 80.65%
5/6/18 68 68 100.00% 50 73.53%
5/5/18 87 87 100.00% 50 57.47%
5/4/18 83 83 100.00% 50 60.24%

This experiment is at an end. So, what did I learn?

  • Reviews and recommendations of these organizations from several years ago may not be currently valid.
  • Don’t put all of your eggs in one basket. If I had to do this for a company to get the numbers up for Google to start recommending it, at least 10K installs – I’d have multiple companies do installs; not just one that could fail.
  • Most of the companies that I examined had similar setups as CPIMobi, so I don’t know that I’d have been able to have better communication with them.
  • Don’t do a wire transfer of money to companies. When my store bough Visa card (as I didn’t want to use my credit cards), they recommended doing a wire transfer or sending a copy of a driver’s license (or passport). I was certainly not going to send them any of my personal documentation. With the failure of this experiment, the wire transfer would just have the money in their pocket without any recourse.

 

 

UI confusion is bad. Moving to RecyclerView – Part 2

As I wrote in part 1, I needed to fix my UI on my app on the method to get more movies to list. In the current production version of the app I’ve got a footer that’s used along with the RealmRecyclerView. The problem with that is that it just puts it along side with the other items in the grid: it could be any of them – which makes the mental load of determining how to get more movies loaded. I went about converting the RealmRecyclerView into a straight RecyclerView using the Realm adapter.

I was able to do that just fine and the button now is taking the entire bottom row. The problem came in is where the button is active, but there isn’t any more data. I need to make the button disabled to indicate to the user that there simply isn’t any more movies to get. This is likely to occur relatively quickly on new movie releases as there are a small number of those.

I went looking for a way to keep track of the footer ViewHolder’s so that I could alter the button state. Searching around, I really didn’t find anything on how to do this.

Note: if you do have a link that does this, please send it to me.

While looking I did find this over at Stack Overflow – a way to do “endless” scrolling with a RecyclerView. I’ll say that this is better than a button – the user doesn’t have to think, it just works. Nice!

With this I removed the new class (RecyclerViewTypes) that kept track of what view holder type it as – as it was no longer needed.

I changed the Adapter to use the now only my ViewHolder instead of the base class RecyclerView.ViewHolder

public class AdapterRecyclerViewRealmMovieNew extends RealmRecyclerViewAdapter<(realm class),
        ViewHolderMovie> {

I then altered the other Adapter method signatures to use the ViewHolderMovie. In onCreateViewHolder and onBindViewHolder I removed the other ViewHolders. I removed these methods: getItemViewType, getItemCount to go back to using the base class methods.

In the fragment that held the Adapter, I removed the footer ViewHolder, Button and mLayoutManager.setSpanSizeLookup method – as they were no longer needed. I removed the button listener, removed the calls to set and remove the button listener. This made the fragment smaller and certainly much easier to read.

Here is the part that I added was the new addOnScrollListener. My version looks slightly different than the on SO article though:

        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                mScrollVisibleItemCount = mRecyclerView.getChildCount();
                mScrollTotalItemCount = mLayoutManager.getItemCount();
                mScrollFirstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();

                if (mScrollLoading) {
                    if (mScrollTotalItemCount > mPreviousTotal) {
                        mScrollLoading = false;
                        mPreviousTotal = mScrollTotalItemCount;
                    }
                }
                if (!mScrollLoading && (mScrollTotalItemCount - mScrollVisibleItemCount)
                        <= (mScrollFirstVisibleItem + mScrollVisibleThreshold)) {
                    // End has been reached

                    // Load more data
                    Log.e(TAG, "onScrolled end called");

                    // Do something
                    mScrollLoading = true;
                }
            }
        });

 

The reason for the slightly different look is that when switching movie data doesn’t reset the variables set in the scroll listener, so I had to create a method that was called each time the data was reset:

 

// This is the number of elements where you want the fetch data item to fire
private final int mScrollVisibleThreshold = 5;
private int mPreviousTotal = 0;
private boolean mScrollLoading = true;
private int mScrollFirstVisibleItem = 0;
private int mScrollVisibleItemCount = 0;
private int mScrollTotalItemCount = 0;

/***
 * Reset the scroll totals
 * NOTE: If this isn't done then the scroll will not work properly when the data is reset
 */
private void resetScrollTotals() {
    mPreviousTotal = 0;
    mScrollLoading = true;
    mScrollFirstVisibleItem = 0;
    mScrollVisibleItemCount = 0;
    mScrollTotalItemCount = 0;
}

This took some time to figure out, once done the scrolling worked great!

The user experience is even better now that users can scroll with ease and not need to think about pressing a button to get more data.

Advertising my app: Getting a larger install count – Part 3

Part 3 to my prior experience with CPIMobi.

In the 14 days since the last post on this, CPIMobi was has added 25 installs.

So truly pathetic.

Time to ask for a refund. I thought you might want to see the result of this fabulous buy.

Date Impressions Clicks CTR Installations CR
6/14/18 108 2 1.85% 1 50.00%
6/13/18 42 3 7.14% 2 66.67%
6/12/18 21 1 4.76% 1 100.00%
6/11/18 51 3 5.88% 2 66.67%
6/10/18 47 2 4.26% 2 100.00%
6/9/18 19 1 5.26% 1 100.00%
6/8/18 8 1 12.50% 0 0.00%
6/7/18 4 4 100.00% 2 50.00%
6/6/18 2 2 100.00% 2 100.00%
6/5/18 4 3 75.00% 3 100.00%
6/4/18 10 2 20.00% 2 100.00%
6/3/18 7 4 57.14% 3 75.00%
6/2/18 18 7 38.89% 3 42.86%
6/1/18 3 1 33.33% 1 100.00%
5/31/18 2 2 100.00% 2 100.00%
5/30/18 6 1 16.67% 1 100.00%
5/29/18 18 3 16.67% 3 100.00%
5/28/18 2 1 50.00% 1 100.00%
5/27/18 11 9 81.82% 7 77.78%
5/26/18 8 8 100.00% 4 50.00%
5/25/18 2 2 100.00% 0 0.00%
5/24/18 7 6 85.71% 5 83.33%
5/23/18 5 5 100.00% 4 80.00%
5/22/18 4 4 100.00% 3 75.00%
5/21/18 4 3 75.00% 2 66.67%
5/20/18 8 8 100.00% 7 87.50%
5/19/18 8 8 100.00% 6 75.00%
5/18/18 10 10 100.00% 8 80.00%
5/17/18 10 8 80.00% 5 62.50%
5/16/18 6 5 83.33% 3 60.00%
5/15/18 10 10 100.00% 9 90.00%
5/14/18 5 4 80.00% 2 50.00%
5/13/18 18 16 88.89% 10 62.50%
5/12/18 22 22 100.00% 13 59.09%
5/11/18 13 13 100.00% 8 61.54%
5/10/18 17 17 100.00% 11 64.71%
5/9/18 40 22 55.00% 17 77.27%
5/8/18 45 43 95.56% 36 83.72%
5/7/18 64 62 96.88% 50 80.65%
5/6/18 68 68 100.00% 50 73.53%
5/5/18 87 87 100.00% 50 57.47%
5/4/18 83 83 100.00% 50 60.24%

 

UI confusion is bad. Moving to RecyclerView – Part 1

I’ve noted before that while the RealmRecyclerView works well, however the add button does not work – at all. I noted in the prior post on this that I’d just add the more data button into the footer; The way that the RealmRecyclerView works is the footer loads the footer view into the next grid cell.

I went looking at the loading of more movie data on my app recently and thought with it having either 2 or 3 columns – depending on the width of the device (and device type) – the load more data could be in any one of those columns. Thinking as a new user might, this really is poor UI design, possibly confusing the user.

I decided at that point to move the button to the bottom of the RealmRecyclerView. The problem was, when I moved it down to the bottom, by adding a new button on the bottom of the view containing the RealmRecyclerView. That worked fine – the button was the length of the main view that contained the RealmRecyclerView and was not confusing. The problem is that the button didn’t come at the end of the RealmRecyclerView, but at the bottom of the view. That’s fine and all, but on small devices, this would remove some the viewing space. Time to roll my own RealmRecycler view that I talked about back in the prior post.

Realm now has a method to deal with data for ListViews and what I need a RealmRecycler view. Note that for release 3.0.0 needs to use at minimum on Realm version 5.0.0 – I learned this the hard way 🙂 I end up using version 2.1.0 – which works just fine with my current Realm 4.2.

Remove this from your app’s build.gradle file (if you don’t have any other uses):

implementation 'com.github.m2f.realm-recyclerview:library:1.4'

This goes into your app’s build.gradle file:

implementation 'io.realm:android-adapters:2.1.0'

I created a brand new adapter, very similar to the RealmRecyclerView adapter. I noted that I needed a way to differentiate between the “normal” view and the footer. I created a new class that dealt with the different types. While I’m not using a header, I thought it would be a good idea to have one just in case:

public class RecyclerViewTypes {
    public static final int TYPE_HEADER = 0;
    public static final int TYPE_NORMAL = 1;
    public static final int TYPE_FOOTER = 2;
}

 

The adapter class definition

public classAdapterRecyclerViewRealmMovieNew extends RealmRecyclerViewAdapter<(realm class),
        RecyclerView.ViewHolder> {

The use of the types is here on which item to inflate:

@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
  mContext = viewGroup.getContext();
  View v = null;
 
  if (viewType == RecyclerViewTypes.TYPE_FOOTER) {
     v = LayoutInflater.from(mContext).inflate(R.layout.movie_list_footer, viewGroup, false);
     ViewHolderMovieFooter vhmf = new ViewHolderMovieFooter(v);
     if (vhmf.buttonMovieListFooter != null && mButtonFooterClickListener != null) {
        vhmf.buttonMovieListFooter.setOnClickListener(mButtonFooterClickListener);
     }
     return vhmf;
  }
 
  //if (viewType == RecyclerViewTypes.TYPE_NORMAL)
  v = LayoutInflater.from(mContext).inflate(R.layout.grid_movie_single, viewGroup, false);
  ViewHolderMovie vh = new ViewHolderMovie(v, new ViewHolderClicks() {
     @Override
     public void onViewClick(View v, int position, String sourceLocation) 
      ...
     }
  });
  return vh;
 }

In my fragment, I created a click listener and passed it into this adapter and set it in the variable mButtonFooterClickListener. In the click listener, I have a method to eventually calls the JobScheduler to get the movie data.

This will return which type to use:

...
    @Override
    public int getItemViewType(int position) {
        if (isPositionFooter(position)) {
            return RecyclerViewTypes.TYPE_FOOTER;
        }

        return RecyclerViewTypes.TYPE_NORMAL;
    }

    private boolean isPositionHeader(int position) {
        return position == 0;
    }

    private boolean isPositionFooter(int position) {
        if (getData() == null) {
            return true;
        }

        int footerSize = getData().size();

        return position >= footerSize;
    }
...

FYI – the data is set in the base class adapter. You can get the data from base class via getData().

On your binding view holder, you won’t get the view type to use as an argument;  however using the ViewHolder itself, that class type can be interrogated to determine what to do:

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
    String moviePosterPath = null;

    if (viewHolder instanceof ViewHolderMovieFooter) {
        ViewHolderMovieFooter vhmf = (ViewHolderMovieFooter) viewHolder;
        return;
    }

    if (viewHolder instanceof ViewHolderMovie) {
        if (getData() == null) {
            return;
        }

        ViewHolderMovie myMovieHolder = (ViewHolderMovie) viewHolder;
...
    }
}

 

Now you need to take into account the extra item in your RecyclerView. In my case, I need to add one, to account for the new footer. If you have both the footer and header, you will need to add two.

    @Override
    // Add two more counts to accommodate header and footer
    public int getItemCount() {
        int size = 0;

        // Note: no header in this one!
        if (getData() != null) {
            size = getData().size();
        }

        //  This is only needed if there is a footer
        size += 1;

        return size;
    }

 

In your Fragment or Activity replace the m2f RealmBasedRecyclerViewAdapter adapter with Realm’s RealmRecyclerViewAdapter adapter.

public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
 Bundle savedInstanceState) {
......
     realmRecyclerView = (RealmRecyclerView) view.findViewById(R.id.realm_recycler_view);
     mGetMoreDataButton = (Button) view.findViewById(R.id.get_more_movie_data_button);
......
     (have your Realm instance ready)
     RealmResults<(realm class)> movieResults = getMovieResults(mRealm);
 
     mMovieRecyclerViewAdapter = new AdapterRecyclerViewRealmMovieNew(movieResults, true, true);
     mMovieRecyclerViewAdapter.setButtonFooterClickListener(mGetMoreDataListener);

 

Now we have to determine the column count. I found out a dynamic creating column count. Here is a nice way to do exactly that.

ColumnQty columnQty = new ColumnQty(R.layout.grid_movie_single, this.getContext());
final int columnCount = columnQty.calculateNoOfColumns();
final GridLayoutManager mLayoutManager = new GridLayoutManager(this.getContext(), columnCount);

This will finish the remainder of the setup:

mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mMovieRecyclerViewAdapter);
mRecyclerView.setHasFixedSize(false);

 

This works, unfortunately this works exactly like the current app and sticks the view with the get more data button in the next free grid cell.

While doing some research to have view that takes up the entire row of the grid, I found this which customizes how the layout manager works. My code:

mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
    @Override
    public int getSpanSize(int position) {
        switch(mMovieRecyclerViewAdapter.getItemViewType(position)){
            case RecyclerViewTypes.TYPE_NORMAL:
                return 1;
//            case RecyclerViewTypes.TYPE_HEADER:
//                return mLayoutManager.getSpanCount();
            case RecyclerViewTypes.TYPE_FOOTER:
                return mLayoutManager.getSpanCount();
            default:
                return -1;
        }
    }
});

For the normal (non footer) cell, it will use the default span of 1. If it is the footer, then we want to use the entire row, so getting the entire span count (from where we setup the GridLayoutManager (specifically the columnCount), this will take up the entire row. Putting this setup before the setting the layout manager, the view with the get more data button now takes the entire row. This will make it easy for the user to get more data.

 

In my final testing, I found one problem with this – how can I disable the button when there isn’t any more movies to get from the provider.

 

This will have to wait for part 2.

 

 

 

Xamarin Forms with Visual Studio 2017

Looking at the local Indianapolis market, companies (generally) want multi-platform mobile solutions which means either Xamarin Forms solutions (via Visual Studio) or mobile Javascript solutions (Ionic, React, etc.)

I already had a wireframe solution for my next app and I decided to go with Xamarin. I used MockFlow to do it with; I would have used Balsamiq if it were free and/or more wireframes to create.

As things are moving fast, I decided to skip the book and go online training route. Now Microsoft has their own Xamarin University site, but when registering for a new Microsoft Id, they want a phone number. Now, I don’t have a phone number to give them so choosing that option was out. The other route to go here are the classes offered thru  LinkedIn training (formally Lynda). Note: I am using VS 2017 for Mac. The classes I took so far are:

Xamarin Essential Training
Interesting intro class – the problem is that it uses Visual Studio 2015 and is pretty old. The sample apps are interesting and show how to build iOS, Android and Universal Windows Platform (UWP) solutions. With Visual Studio for Mac, you cannot build UWP solutions and you must exclude them the first thing, or it will not go far. Generally speaking, it’s a good introduction class, but has a glaring problem – MVVM. It mentions that you should use MVVM for your solutions, but doesn’t actually go into how to use them, but just a high level overview of what MVVM is.

 

There is a series of 9 classes named Mastering Xamarin.Forms Development (MXFD). While these MXFD classes use Visual Studio 2017, but an older version of 2017. The problem is that there is only one sample program per class and for most of them they will not compile under VS2017 Mac – because of this problem: Xaml compiler:

../Mastering Xamarin.Forms/Mastering Xamarin.Forms 9/Ex_Files_Xamarin_Dev_Pt_9/Exercise Files/Paperboy/packages/Xamarin.Forms.2.3.3.180/build/portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20/Xamarin.Forms.targets(62,3): error MSB4018: The "XamlCTask" task failed unexpectedly.
../Mastering Xamarin.Forms/Mastering Xamarin.Forms 9/Ex_Files_Xamarin_Dev_Pt_9/Exercise Files/Paperboy/packages/Xamarin.Forms.2.3.3.180/build/portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20/Xamarin.Forms.targets(62,3): error MSB4018: System.IO.FileNotFoundException: Could not find file "../Xamarin/Mastering Xamarin.Forms/Mastering Xamarin.Forms 9/Ex_Files_Xamarin_Dev_Pt_9/Exercise Files/Paperboy/Paperboy/obj/Debug/Paperboy.dll.mdb"
../Mastering Xamarin.Forms/Mastering Xamarin.Forms 9/Ex_Files_Xamarin_Dev_Pt_9/Exercise Files/Paperboy/packages/Xamarin.Forms.2.3.3.180/build/portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20/Xamarin.Forms.targets(62,3): error MSB4018: File name: '../Xamarin/Mastering Xamarin.Forms/Mastering Xamarin.Forms 9/Ex_Files_Xamarin_Dev_Pt_9/Exercise Files/Paperboy/Paperboy/obj/Debug/Paperboy.dll.mdb'

Now if you look at the these (and other similar links), there is a problem with MSBuild that they created when “upgrading” something inside of it. Some of the comments are that it can build using the old versions, but I couldn’t figure out how to do that and get it to work. I think at this point it’s too far gone to fix without re-creating the project from scratch.

I did complain to LinkedIn (LI) about the programs not compiling via the comments, but given no response, I don’t think that anyone at LI is actually reading them.

Mastering Xamarin.Forms Development: 1 Pages, Layout, and Navigation

Mastering Xamarin.Forms Development: 2 Basic Controls and Plugins

Mastering Xamarin.Forms Development: 3 Styles and Theming
Sample file: does not compile

Mastering Xamarin.Forms Development: 4 Binding, Command, and Converters
Sample file: does not compile

Mastering Xamarin.Forms Development: 5 Dependency Services

Mastering Xamarin.Forms Development: 6 Data and Storage Strategies
Sample file: does not compile

Mastering Xamarin.Forms Development: 7 Push Notifications
Sample file: does not compile

Mastering Xamarin.Forms Development: 8 Renderers, Behaviors, and Effects
Sample file: does not compile

Mastering Xamarin.Forms Development: 9 Advanced Push Notifications
Sample file: does not compile

Generally speaking – most of these classes are really good. Problems (other than the one with you can’t compile most of these solutions) with the solution is that it there are bits commented out and it looks like it won’t compile without them. Also you must provide a API key, which I don’t think most people would want to bother getting. Noting the MVVM issue from the intro class above – this does use it, but given the compile problem, commented out code problem makes using this as an MVVM example a non-starter.

I’m still looking for a good Xamarin MVVM example – if you know one, please let me know.

The nice thing about using LI as your training provider is that excepting Xamarin Essential Training – all of the classes were added to my LI profile without problems. I did see problems with some of the coursework not registering that I completed a chapter (the reason with not getting the Xamarin Essential Training to register completed); it mostly worked if I refreshed it multiple times and then looked at it the next day or two.

 

Visual Studio

I’ve not used Visual Studio (VS) in a while: since I’ve not used it since VS 2008. Going from Android Studio (based on IntelliJ IDEA editor) to VS 2017 for Mac was like taking a step back to 2008. You can’t by default have two solutions open in different VS windows. If you do do that via the open solution options or via bash window command, the solution will look different than if only a single solution was open. The error window can’t be pinned open. When you compiled and get an error – you can’t do a control (or command) click on the error message; however you can click on the side error panel side which will take you to the file. Sometimes it will go into the XML compile file and that error will lead nowhere. Sometimes you can do a control (or command) click on the class, sometimes you need to do a F12 – really odd. 

It’s just a UI mess; like going from most any nice 2018 car back to a Model T that had many wonky items bolted onto it.

I hear how people love VS, they really must be using the Windows version.

Advertising my app: Getting a larger install count – Part 2

Part 1 to my prior experience with CPIMobi.

In the 11 days since the last post on this, CPIMobi was has added 55 installs. Currently, there is 635 installs left to go. Here is a chart:

Date Impressions Clicks CTR Installations CR
5/30/18 6 1 16.67% 1 100.00%
5/29/18 18 3 16.67% 3 100.00%
5/28/18 2 1 50.00% 1 100.00%
5/27/18 11 9 81.82% 7 77.78%
5/26/18 8 8 100.00% 4 50.00%
5/25/18 2 2 100.00% 0 0.00%
5/24/18 7 6 85.71% 5 83.33%
5/23/18 5 5 100.00% 4 80.00%
5/22/18 4 4 100.00% 3 75.00%
5/21/18 4 3 75.00% 2 66.67%
5/20/18 8 8 100.00% 7 87.50%
5/19/18 8 8 100.00% 6 75.00%
5/18/18 10 10 100.00% 8 80.00%
5/17/18 10 8 80.00% 5 62.50%
5/16/18 6 5 83.33% 3 60.00%
5/15/18 10 10 100.00% 9 90.00%
5/14/18 5 4 80.00% 2 50.00%
5/13/18 18 16 88.89% 10 62.50%
5/12/18 22 22 100.00% 13 59.09%
5/11/18 13 13 100.00% 8 61.54%

I wrote CPI Mobi pointing to this article and they replied on 21 May that they would be “working hard to increase the volumes”. I’m not sure about you, but that quote compared with an average of less than 3 installs a day doesn’t inspire much hope. In part 1, I had moved the install count from 50 to 100 installs a day. I had moved the install count from 100 up to 1000 on 21 May hoping that they would get the install count over 1000 prior to the GDPR implementation date of 24 May. Given the over 635 installs remaining, it didn’t happen.

The question is will the 1000 installs occur in the next week? Given the track record so far, it seems doubtful.

A question out to you: have you asked for a reply from Paypal before in a situation like this? If so, drop me a line.