Accelerate App Reviews

Accelerate App Reviews

Asking for review in your app? It’s tricky. If not done with due diligence, on encountering your request to review, users might get annoyed and still worse, they could rate your app low even though they liked it. A simplistic solution of asking for review after N days from installation (or first use) and N days after user has chosen “remind me later”, is not an appropriate solution. Why? User might have installed your app, ran it, exited, came back after N days and they were presented with a review request message, whereas, they had only used the app once. This is not a nice experience. Asking for review when user is exiting the app is less effective because user already has something else in their mind when they are coming out of the app.

Though there is no one solution that fits all, but this is how I try to tackle this scenario in my app. It’s not the “number of days” but “total minutes user has used your app” is my criteria to decide the interval of popping review message. I keep a record of overall usage of the app, and after a designated number of minutes, I pop a request to user. You might have to go through some trial and error to come up with the right “number of minutes” when you want to request. Determine whether your app is highly immersive or a quick open and shut type of app. In an immersive app|game you might want your review requests at longer intervals. In a less immersive app, you would rather ask earlier and at a less frequency. Then, you might want to gradually decrease the interval between requests during session. Don’t forget to code your logic in such a way that you could easily tune request intervals and accumulative usage time and update your app as soon as you realize that a change in the times is required. Here is pseudo-code for indication purpose:


[THIS IS PSEUDO CODE FOR INDICATION ONLY]

constant int POP_REQUEST_AFTER_ACCUMULATIVE_USAGE (adjusting knob) 
constant int POP_REQUEST_INTERVAL (adjusting knob : Initial interval between requests in the session) 
constant int POP_REQUEST_INTERVAL_DECREASE_BY (adjusting knob : Decrease interval after every request) 
constant int POP_REQUEST_INTERVAL_MINIMUM (adjusting knob : interval should not go below this)

int AccumulativeAppUsage (persist this info in storage)
DateTime LastReviewRequest;
Int ReviewRequestInterval = POP_REQUEST_INTERVAL;

App.Start | App.Activate
	DateTime AppStart
	LastReviewRequest = DateTime.Now //reset

App.Stop | App.Deactivate
	AccumulativeAppUsage += DateTime.NOW – AppStart
	Save AccumulativeAppUsage

Page.[identify the event which triggers PopRequest method]
	PopRequest()

PopRequest()
	totalUsageTillNow = AccumulativeAppUsage += DateTime.NOW – AppStart
	if(totalUsageTillNow >= POP_REQUEST_AFTER_ACCUMULATIVE_USAGE)
	{
		If(DateTime.Now – LastReviewRequest >= ReviewRequestInterval)
		{
			Show review request
			LastReviewRequest = DateTime.Now
			If(ReviewRequestInterval > POP_REQUEST_INTERVAL_MINIMUM)
				ReviewRequestInterval -= POP_REQUEST_INTERVAL_DECREASE_BY
		}
	}
Advertisements

C#|.NET : Size String, Truncate Lines

Swiss Crop Circle 2009 Aerial by Kecko, on Flickr
Swiss Crop Circle 2009 Aerial, a photo by Kecko on Flickr.

I needed to truncate lines from CSV string prepared for export through mail in my app. Windows Phone mail restricts text size to 1MB. In my app’s case some CSV reports could go beyond 1MB. To make sure that the CSV report does not exceed a desired size in bytes, I created this extension method – SizeIt. SizeIt can truncate lines (and characters if required) from a given string from beginning or from end and also inserts information text line (how many lines and characters removed) in the resulting string. Following is the test form created to show what the method does to a string. In this example a 300 byte 10 line string is given as input and asked to reduce it to <200 bytes, first from beginning, and then from end.

SizeTail

SizeFront

Here is the code for the extension method:

    public static class StringBuilderExtensions
    {
        /// <summary>
        /// Sizes StringBuilder to given size
        /// </summary>
        /// <param name="sb">StringBuilder</param>
        /// <param name="bytes">Maximum Size of resultant string. Mostly the size of string will be less than max size</param>
        /// <param name="removeLinesFromBeginning">Pass true, if clipping has to happen in the beginning</param>
        public static void SizeIt(this StringBuilder sb, int bytes, bool removeLinesFromBeginning)
        {
            bool _stringReplacementHappened = false;
            bool _maxBytesAdjusted_for_lines = false;
            bool _maxBytesAdjusted_for_chars = false;
            string _insertThismessage = "";
            int _linesRemoved = 0;
            int _charsRemoved = 0;
            //try removing lines.
            while (System.Text.Encoding.Unicode.GetByteCount(sb.ToString()) > bytes)
            {
                char _alternateNewLine = '\r';
                string _workingString = sb.ToString();
                if (!_workingString.Contains(Environment.NewLine) && !_workingString.Contains(_alternateNewLine))
                {
                    break;
                }
                int _newlinelocation = sb.ToString().IndexOf(Environment.NewLine);
                int _lastNewlinelocation = sb.ToString().LastIndexOf(Environment.NewLine);
                if (_newlinelocation <= 0)
                {
                    _newlinelocation = sb.ToString().IndexOf(_alternateNewLine);
                }
                if (_lastNewlinelocation <= 0)
                {
                    _lastNewlinelocation = sb.ToString().LastIndexOf(_alternateNewLine);
                }

                if (removeLinesFromBeginning)
                {
                    sb = sb.Remove(0, _newlinelocation + 1);
                    _linesRemoved++;
                    _stringReplacementHappened = true;
                    _insertThismessage = string.Format("... {0} lines removed{1}", _linesRemoved, Environment.NewLine);
                }
                else
                {
                    int _lastReturnAt = _lastNewlinelocation;
                    int _charsInLastLine = sb.Length - _lastNewlinelocation;
                    sb = sb.Remove(_lastReturnAt, _charsInLastLine);
                    _linesRemoved++;
                    _stringReplacementHappened = true;
                    _insertThismessage = string.Format("{1}{0} lines removed...", _linesRemoved, Environment.NewLine);
                }
                if (!_maxBytesAdjusted_for_lines)
                {
                    bytes -= 50; //40 extra bytes for information text
                    _maxBytesAdjusted_for_lines = true;
                }
            }
            //if it's still more than the desired size
            if (System.Text.Encoding.Unicode.GetByteCount(sb.ToString()) > bytes)
            {
                int _currentBytes = System.Text.Encoding.Unicode.GetByteCount(sb.ToString());
                if (!_maxBytesAdjusted_for_lines)
                {
                    bytes -= 100;
                    _maxBytesAdjusted_for_lines = true;
                    _maxBytesAdjusted_for_chars = true;
                }
                else if (!_maxBytesAdjusted_for_chars)
                {
                    bytes -= 50;
                    _maxBytesAdjusted_for_chars = true;
                }
                int _removeChars = (_currentBytes - bytes) / 2;
                if (removeLinesFromBeginning)
                {
                    sb = sb.Remove(0, _removeChars);
                    _stringReplacementHappened = true;
                    _insertThismessage = string.Format("... {0} lines and {1} chars removed{2}", _linesRemoved, _removeChars, Environment.NewLine);
                }
                else
                {
                    sb = sb.Remove(sb.Length - _removeChars, _removeChars);
                    _stringReplacementHappened = true;
                    _insertThismessage = string.Format("{2}{0} lines and {1} chars removed...", _linesRemoved, _removeChars, Environment.NewLine);
                }
            }
            if (_stringReplacementHappened)
            {
                if (removeLinesFromBeginning)
                {
                    sb.Insert(0, _insertThismessage);
                }
                else
                {
                    sb.Append(_insertThismessage);
                }
            }
        }
    }

You may extend SizeIt to make it even smarter because it has following shortcomings in current version:

  • Does not truncate very closely to Max size. Will always be less than 50 bytes from max.
  • If there are only two lines and line of truncating side is huge, even a slight reduction in size will remove the complete content of the huge line, meaning loosing most of the text. This could be taken care of by firsts analyzing the string and then truncating line/character, as required
  • The logic inherently tries to remove lines first, should intelligently decide whether to remove line or chars.

Navigation Hierarchy

Avirall Time Suite is an extensive app with 60,000+ working LOC in .cs files. I have not calculated the lines of code in XAML because a major portion of them are auto-generated. Still, there is about same number of lines in XAML files, as well. Avirall has 15 pages and 3 of them have about 5 types, so in all about 30 pages show up in Avirall. During documentation I created a high level diagram of navigation hierarchy in the app. I think it gives a bird’s eye view of how the app’s control flow, kind of circulatory system. This is how it looks like :

MTTHNavigation

Windows Phone App Makeover By Design Guru Arturo Toledo

My Twitter.Connect had this surprise mention this morning:

Tweet

Chief Architect of www.Toledo2.com and ex Microsoft UX Guru Arturo Toledo has graciously accepted my request to help make-over my app Avirall. DVLUP and WPCentral have helped me forward my case to Mr Toledo. Thank you so much to all of you! I am looking forward to take Avirall to level^2 with Mr. Toledo’s consultancy.

Before my app was accepted for this design session, I had a chance to attend a fantastic web session by Toledo about how to make Windows Phone apps better UX-wise in general and design-wise in particular. The talk was so informational and encouraging that I felt a need to re-visit Avirall design wise. With inputs from the session and with my limited understanding of subject at hand, I came up with some functionality to improve UX and also some aesthetical changes to make Avirall’s home hub a bit richer. The said updates are in development and getting ready for future versions of Avirall.

Before:

PilcrowIcons

After Open Web Session:

Hub3

Further After Avirall Specific Design Consultancy:

CoingSoon

I think Avirall is going to get a tremendous makeover with inputs from Arturo Toledo. Why do I think so? Because, I believe, for one, if one is so passionate about design it’s going to shine through his inputs for Avirall and for two, if an open web session can have such an impact on me, a one-to-one app specific session can do miracles. Hope I can keep up with his contribution and execute them at the level of his expectation. Here are some of Arturo’s thoughts about design which are sharp and brilliant:

Toledo_1

Toledo_2

Toledo_3

Toledo_4

Toldeo6

Your app can also get a makeover from Arturo Toledo. How? Know more here.

You might also be interested in my earlier posts related to Avirall’s design –  My Fonts, Icons in Accent Color, and Quick Stopwatch Design Iteration.

Deep Copy: JSON Route

Sharp Snippets

Introduction

This post is not about:

  • … when to deep copy and when to shallow copy
  • … answering what will happen to events
  • … design problem when deep copy is overused (do research)
  • … JSON v/s Binary serialization
  • … having extension at object level
  • … performance

This post is about:

Cloning

View original post

Technical Hiring

images

In 2009 I joined an onsite+offshore model software development company. This was my first experience with the offshore environment. The company had just started their operations in India and planned to grow faster. I got into the org as dev lead when it had 3 engineers, 1 designer, and 2 test engineers. Soon after, I was given responsibility to spear-head the company technically, including project delivery and technical hiring. In first 6 months we grew our team to 60 SDE and SDETs and by the end of 12 months we were more than 130. We were primarily doing projects for US clients. Our project ranged from corporate web applications to GIS based systems and QA services.

Though initial screening was done by HR department, technical aspects of the interview and final decision was with me and my team. We typically hired engineers with average 3 years of relevant experience.

This is what went into hiring process:

  • Hiring requirements were raised internally.
  • Requirements usually ranged from 3 to 15 engineers.
  • External agencies informed regarding the requirement.
  • HR received applications from candidates.
  • Applications were shortlisted for preliminary call (40%-50% would get through).
  • HR exec would make calls to shortlisted candidates to set-up initial interview with tech team.
  • 1st round of tech interview would take place on phone (20% would get through).
  • HR exec sets up personal interview for 2nd round of tech interview, usually at company’s office.
  • Our 2nd round tech interviews consisted of a lot of practical white boarding, having following areas of testing:
    • Technical Skills
      • Problem solving (strictly coding)
      • Syntax and semantic understanding
      • Style of coding
      • Structure of solution
      • Understanding of common problems in the domain
      • OOP concepts demonstrated in classes, interfaces, modifiers, methods, properties, events, delegates, etc.
      • Databases, SPs, Indexes, performances, etc.
      • Understanding of IIS, Servers, services, etc.
      • Web dev related stuff – HTML, Javascript, AJAX, JQuery, JSON, XML, etc.
  • Project Management Skills (if requirement was for technical project manager)
    • Team building
    • Project setup – Dev team, test team, dev infra, test infra, etc.
    • Documentation
    • Planning, estimation, delegation, and monitoring
    • Waterfall | SCRUM
    • Client communication
    • Stakeholder collaboration – Arch, Dev, Quality, Deployment, Client, and Top Management
  • Soft Skills
    • Communication
    • Focus
    • Energy
    • Willingness
    • Fit

Only about 5-10 % of total candidates, who applied originally, would get through the final interview and get offer letter.

If you have to fulfill a requirement of 10 engineers, you must get at least 120+ applications.

To fill 100 openings in a year, we screened a whooping 2000+ candidates. I still remember 5-10 senior devs along with HR team would work on many weekends for tech interviews. I will try to asemble the questions we used to ask for technical and managerial rounds and share with you here.

Get hired!