Nikola Brežnjak blog - Tackling software development with a dose of humor
  • Home
  • Daily Thoughts
  • Ionic
  • Stack Overflow
  • Books
  • About me
Home
Daily Thoughts
Ionic
Stack Overflow
Books
About me
  • Home
  • Daily Thoughts
  • Ionic
  • Stack Overflow
  • Books
  • About me
Nikola Brežnjak blog - Tackling software development with a dose of humor
iOS

How to use fastlane pem to automatically generate and renew your VoIP push notification profiles

In the previous tutorial, I showed you how easy it is to use fastlane pem to automatically generate and renew your push notification profiles.

In this tutorial, I’ll do the same thing, but for VoIP notifications. I found the solution in this Github issue.

In your project’s root folder create a new folder called fastlane.

Then, create a file called Fastfile in the fastlane folder.

Put the following content into Fastfile:

lane :voip_cert do
    app_identifier = "com.nikola.mygreatapp"
    apple_id = "[email protected]"
    output_path = "."
    p12_password = "password"

    UI.important "Creating a new push certificate for app '#{app_identifier}'."

    Spaceship.login(apple_id)
    Spaceship.client.select_team
    csr, pkey = Spaceship::Certificate.create_certificate_signing_request
    cert = Spaceship::Certificate::VoipPush.create!(csr:csr,bundle_id:app_identifier)

    x509_certificate = cert.download
    certificate_type = "voippush"
    filename_base = "#{certificate_type}_#{app_identifier}"

    p12_cert_path = File.join(output_path, "#{filename_base}.p12")
    p12 = OpenSSL::PKCS12.create(p12_password, certificate_type, pkey, x509_certificate)
    File.write(p12_cert_path, p12.to_der)
    UI.message("p12 certificate: ".green + Pathname.new(p12_cert_path).realpath.to_s)

    x509_cert_path = File.join(output_path, "#{filename_base}.pem")
    File.write(x509_cert_path, x509_certificate.to_pem + pkey.to_pem)
    UI.message("PEM: ".green + Pathname.new(x509_cert_path).realpath.to_s)
end

Make sure you enter the appropriate app_identifier and apple_id and password (if you’re using it).

Execute the following command from your app’s root directory:

fastlane voip_cert

You should see something like this in the output:

nikola in ~/DEV/TheOnlyAppThatMatters on git:feature/make-app-great-again ✖︎ [06:06:06]
→ fastlane voip_cert
[06:06:06]: Get started using a Gemfile for fastlane https://docs.fastlane.tools/getting-started/ios/setup/#use-a-gemfile
[06:06:06]: Driving the lane 'voip_cert' ?
[06:06:06]: Creating a new push certificate for app 'com.nikola.mygreatapp'.
[06:06:06]: p12 certificate: /Users/nikola/DEV/TheOnlyAppThatMatters/fastlane/voippush_com.nikola.mygreatapp.p12
[06:06:06]: PEM: /Users/nikola/DEV/TheOnlyAppThatMatters/fastlane/voippush_com.nikola.mygreatapp.pem
[06:06:06]: fastlane.tools finished successfully ?

And that’s it! Hope this proves to be helpful for you! ?

How to use fastlane pem to automatically generate and renew your #VoIP push notification profiles https://t.co/AvbKkbhRWD

— Nikola Brežnjak (@HitmanHR) September 7, 2017

iOS

How to use fastlane pem to automatically generate and renew your push notification profiles

This is a short tip which will save you a lot of time!

Ever gone through the tedious task of manually creating and maintaining your push notification profiles for your iOS apps? Tutorials like this one were very useful, but nowadays thanks to fastlane pem, you can do it with a single command.

The pem tool creates new .pem, .cer, and .p12 files to be uploaded to your push server if a valid push notification profile is needed.

To install pem run:

sudo gem install fastlane

Also, make sure you have the latest version of the Xcode command line tools installed:

xcode-select --install

Then, just run fastlane pem and it will ask you information about the app for which you wish to renew the certificates.

You can pass parameters like this:

fastlane pem -a com.mygreat.app -u username

If you want to generate a development certificate run:

fastlane pem --development

To get a list of available options run:

fastlane pem --help

?

If you’re looking to generate the same thing but for VoIP push notification, then check out this tutorial.

How to use fastlane pem to automatically generate and renew your #push notification profiles https://t.co/cutGxIDH6Y

— Nikola Brežnjak (@HitmanHR) September 7, 2017

Books

Resilience: Hard-Won Wisdom for Living a Better Life

My favorite quotes from the book Resilience: Hard-Won Wisdom for Living a Better Life by Eric Greitens which I rated 5/5 on my Goodreads account:

What you will become is a result of what you are willing to endure.

Use what hits you to change your trajectory in a positive direction.

The world breaks everyone and afterward many are strong at the broken places. But those that will not break it kills.

God, grant me the serenity to accept the things I cannot change, Courage to change the things I can, And wisdom to know the difference.

Great changes come when we make small adjustements with great conviction.

Action without direction rarely leads to progress.

Anyone who does anything meaningful will have critics.

A focus on excellence will, over time, lead to happiness.

Not what we have, but what we enjoy constitues our abundance.

Any fool can learn from his mistakes. Wise man learns from mistakes of others.

If you want to feel differently – act differently! At first it will feel like a lie, but ‘be’ what you would like to be and you’ll become it.

People who think you’re weak will offer you an excuse. People who respect you will offer you a challenge.

If you want to live a purposeful life, you have to create your purpose.

Working hard yourself makes you more appreciative and respectful of the hard work of others.

He who fears he shall suffer, already suffers what he fears.

  1. Why am I here?
  2. What’s going around me?
  3. What am I going to do about it?
  4. How will my actions affect others?

When the child is skillful he boasts. When the warrior has a skill, he teaches. When the child is strong, he attacks the weak. When the warrior is strong, he protects the weak. The child criticises others to make himself feel better. The warrior is his own most demanding critic.

Warriors are expected to combine courage and ferocity in battle with decency and humanity in victory.

My #notes from a great #book Resilience Hard-Won Wisdom for Living a Better Life by Eric Greitens https://t.co/HSJDYOniIj

— Nikola Brežnjak (@HitmanHR) September 4, 2017

Programming

Code Complete 2 – Steve McConnell – Defensive programming ?️

I just love Steve McConnell’s classic book Code Complete 2, and I recommend it to everyone in the Software ‘world’ who’s willing to progress and sharpen his skills.

Other blog posts in this series:

  • Part 1 (Chapters 1 – 4): Laying the Foundation
  • Chapter 5: Design in Construction
  • Chapter 6: Working Classes
  • Chapter 7: High-Quality Routines

Defensive-programming techniques make errors easier to find, easier to fix, and less damaging to production code.

Assertions

An assertion is a code that’s used during development-usually a routine or macro- that allows a program to check itself as it runs. When an assertion is true, that means everything is operating as expected.

Use error handling code for conditions you expect to occur and assertions for conditions that should never occur.

Design by contract

Preconditions and post conditions are part of an approach to program design and development known as “design by contract”.

  • Preconditions are properties that the client code of a routine or class promises will be true before it calls the routine or instantiates the object.
  • Postconditions are the properties that the routine or class promises will be true when it concludes executing.

For highly robust code, assert and then handle the error anyway.

Some experts argue that only one kind is needed. But real-world programs and projects tend to be too messy to rely solely on assertions.

Error-Handling Techniques

Depending on the specific circumstances, you might want to:

  • Return a neutral value
  • Substitute the next piece of valid data
  • Return the same answer as the previous time
  • Substitute the closest legal value
  • Log a warning message to a file
  • Return an error code
  • Call an error-processing routine/object
  • Shut down

Correctness means never returning an inaccurate result; returning no result is better than returning an inaccurate result.

Robustness means always trying to do something that will allow the software to keep operating, even if that leads to results that are inaccurate sometimes.

Safety-critical applications tend to favor correctness to robustness.

Consumer applications tend to favor robustness to correctness. Any result whatsoever is usually better than the software shutting down.

Exceptions

Exceptions have an attribute in common with inheritance: used judiciously, they can reduce complexity. Used imprudently, they can make code almost impossible to follow.

What you should know about using exceptions:

  • Use exceptions to notify other parts of the program about errors that should not be ignored
  • Throw an exception only for conditions that are truly exceptional
  • Don’t use an exception to pass the buck – handle error locally
  • Avoid throwing exceptions in constructors and destructors unless you catch them in the same place
  • Throw exceptions at the right level of abstraction

Use barricades

Defining some parts of the software that works with dirty data and some that work with clean data can be an effective way to relieve the majority of the code of the responsibility for checking for bad data.

Debugging Aids

Another key aspect of defensive programming is the use of debugging aids, which can be a powerful ally in quickly detecting errors.

Be willing to trade speed and resource usage during development in exchange for built-in tools that can make development go more smoothly.

Determining how much defensive programming to leave in production code

One of the paradoxes of defensive programming is that during development, you’d like an error to be noticeable-you’d rather have it be obnoxious than risk overlooking it. But during production, you’d rather have the error be as unobtrusive as possible, to have the program recover or fail gracefully.

Here are some guidelines for deciding which defensive programming tools to leave in your production code and which to leave out:

  • Leave the code that checks for important errors
  • Remove code that checks for trivial errors
  • Remove code that results in hard crashes
  • Leave the code that helps the program crash gracefully

Being defensive about defensive programming

Too much defensive programming creates a problem of its own. Code installed for defensive programming is not immune to defects, and you’re just as likely to find a defect in defensive-programming code as in any other code-more likely if you write the code casually.

My #notes from a great #book CC 2 by Steve McConnell chapter on Defensive programming ?️ https://t.co/KnBqvEySJd

— Nikola Brežnjak (@HitmanHR) September 4, 2017

Programming

Code Complete 2 – Steve McConnell – High-Quality Routines

I just love Steve McConnell’s classic book Code Complete 2, and I recommend it to everyone in the Software ‘world’ who’s willing to progress and sharpen his skills.

Other blog posts in this series:

  • Part 1 (Chapters 1 – 4): Laying the Foundation
  • Chapter 5: Design in Construction
  • Chapter 6: Working Classes

Routines are used to:

  • reduce complexity
  • introduce an intermediate, understandable abstraction
  • avoid duplicate code
  • hide sequences
  • hide pointer operations
  • improve portability
  • simplify complicated boolean tests and
  • improve performance

Functional cohesion

Functional cohesion is when a function performs one and only one operation.

Cohesions that are considered to be less than ideal:

  • Sequential cohesion
    • exist when a routine contains operations that must be performed in a specific order, that share data from step to step.
  • Communicational cohesion
    • occurs when operations in a routine make use of the same data and aren’t related in any other way.
  • Temporal cohesion
    • occurs when operations are combined into a routine because they are all done at the same time. Think of temporal routines as organizers of other events.

Unacceptable kinds of cohesion:

  • Procedural cohesion
  • Logical cohesion
  • Coincidental cohesion

Good routine names and length

Research shows that the optimum average length for a variable name is 9-15 characters and 15-20 for a routine name.

To name the function use the description of the return value. In object-oriented languages, you don’t need to include the name of the object in the procedure name because the object itself is included in the call.

Avoid names like: document.PrintDocument().

A large percentage of routines in object-oriented programs will be accessor routines, which will be very short. From time to time, a complex algorithm will lead to a longer routine, and in those circumstances, the routine should be allowed to grow organically up to 100 – 200 lines.

Don’t write routines longer than about 200 lines.

How to use routine parameters

Interfaces between routines are some of the most error-prone areas of the program. Here are a few guidelines for minimizing interface problems:

  • Put parameters in input-modify-output order
  • If several routines use similar parameters, put the similar parameters in a consistent order
  • Use all the parameters
  • Put status or error variables last
  • Limit the number of a routine’s parameters to about seven

Psychological research has found that people generally cannot keep track of more than about seven chunks of information at once. (Miller 1956.)

The difference between function and procedure is that procedure doesn’t return a value.

You should use macro routines with care and only as a last resort.

Ionic

How to use deep linking in Ionic 1 apps with Ionic Native Deeplinks plugin

In this tutorial, I’m going to show you how to use deep linking in Ionic 1 apps with Ionic Native Deeplinks plugin that’s originally made for Ionic 2 and Ionic 3. Hopefully, I’ll save you some time so that you won’t have to figure this out on your own. I’ll note where the official documentation is lacking in terms of setting the correct link when passing additional parameters.

So, let’s get started!

Demo project

You can check out the code for this project on Github.

Here’s the app in action, where you’ll notice how by clicking the link you are taken to the app to a specific screen. Also, additional nesting (opening up the detail screen of one ‘chat’) also works:

Step by step

Here are the steps you can take to get to the same final working project like the one I’ve posted on Github.

Start a new Ionic 1 project based on tabs template:

ionic start IonicDeeplinkTest tabs

Add the deeplinks plugin:

ionic plugin add ionic-plugin-deeplinks --variable URL_SCHEME=nikola --variable DEEPLINK_SCHEME=http --variable DEEPLINK_HOST=nikola-breznjak.com --save

Few things to note here are:

  • URL_SCHEME – a string which you’ll put in your links so that once clicked your phone’s operating system will know to open your app. In my case, the links will look like nikola://something
  • DEEPLINK_SCHEME – most probably you’ll want to put https here, but I’ve put http because my website doesn’t (yet) have SSL support ?
  • DEEPLINK_HOST – you should put your domain here on which you’ll put the link

The output of the command above should be something like this:

Fetching plugin "ionic-plugin-deeplinks" via npm

Installing "ionic-plugin-deeplinks" for ios

Installing dependency packages: 

{
  "mkpath": ">=1.0.0",
  "xml2js": ">=0.4",
  "node-version-compare": ">=1.0.1",
  "plist": ">=1.2.0"
}

Now, as mentioned in the official plugin docs, this plugin is originally provided through Ionic Native for Ionic 2+ apps. But, we can use Ionic Native with Ionic 1 if we install it like this (official docs on this subject):

npm install ionic-native --save

Now, copy ionic.native.min.js from node_modules/ionic-native/dist folder into a new lib/ionic-native folder.

Then, in index.html add:

<script src="lib/ionic-native/ionic.native.min.js"></script>

just before the

<script src="cordova.js"></script>

line.

Next, run the following command:

npm install --save @ionic-native/deeplinks

Finally, open up the app.js file and add the following code inside the platform.ready callback:

$cordovaDeeplinks.route({
    '/chats/:chatId': {
        target: 'tab.chat-detail',
        parent: 'tab.chats'
    },
    '/account': {
        target: 'tab.account',
        parent: 'tab.account'
    },
    '/chats': {
        target: 'tab.chats',
        parent: 'tab.chats'
    }
}).subscribe(function(match) {
    $timeout(function() {
        $state.go(match.$route.parent, match.$args);

        if (match.$route.target != match.$route.parent) {
            $timeout(function() {
                $state.go(match.$route.target, {chatId: match.$args.chatId});
            }, 800);
        }
    }, 100); // Timeouts can be tweaked to customize the feel of the deeplink
}, function(nomatch) {
    console.warn('No match', nomatch);
});

Also, don’t forget to add ionic.native to the angular.module function in the app.js file:

angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'ionic.native'])

and inject $cordovaDeeplinks in the .run function.

Just for reference, the full contents of the app.js file should now look like this:

// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'ionic.native'])

.run(function($ionicPlatform, $cordovaDeeplinks, $timeout, $state) {
    $ionicPlatform.ready(function() {
        // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
        // for form inputs)
        if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
            cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
            cordova.plugins.Keyboard.disableScroll(true);

        }
        if (window.StatusBar) {
            // org.apache.cordova.statusbar required
            StatusBar.styleDefault();
        }

        $cordovaDeeplinks.route({
            '/chats/:chatId': {
                target: 'tab.chat-detail',
                parent: 'tab.chats'
            },
            '/account': {
                target: 'tab.account',
                parent: 'tab.account'
            },
            '/chats': {
                target: 'tab.chats',
                parent: 'tab.chats'
            }
        }).subscribe(function(match) {
            console.log('matching');
            console.dir(match);
            $timeout(function() {
                $state.go(match.$route.parent, match.$args);

                if (match.$route.target != match.$route.parent) {
                    $timeout(function() {
                        $state.go(match.$route.target, {chatId: match.$args.chatId});
                    }, 800);
                }
            }, 100); // Timeouts can be tweaked to customize the feel of the deeplink
        }, function(nomatch) {
            console.warn('No match', nomatch);
            console.dir(nomatch);
        });
    });
})

.config(function($stateProvider, $urlRouterProvider) {

    // Ionic uses AngularUI Router which uses the concept of states
    // Learn more here: https://github.com/angular-ui/ui-router
    // Set up the various states which the app can be in.
    // Each state's controller can be found in controllers.js
    $stateProvider

    // setup an abstract state for the tabs directive
        .state('tab', {
        url: '/tab',
        abstract: true,
        templateUrl: 'templates/tabs.html'
    })

    // Each tab has its own nav history stack:

    .state('tab.dash', {
        url: '/dash',
        views: {
            'tab-dash': {
                templateUrl: 'templates/tab-dash.html',
                controller: 'DashCtrl'
            }
        }
    })

    .state('tab.chats', {
            url: '/chats',
            views: {
                'tab-chats': {
                    templateUrl: 'templates/tab-chats.html',
                    controller: 'ChatsCtrl'
                }
            }
        })
        .state('tab.chat-detail', {
            params: {chatId: 0},
            url: '/chats/:chatId',
            views: {
                'tab-chats': {
                    templateUrl: 'templates/chat-detail.html',
                    controller: 'ChatDetailCtrl'
                }
            }
        })

    .state('tab.account', {
        url: '/account',
        views: {
            'tab-account': {
                templateUrl: 'templates/tab-account.html',
                controller: 'AccountCtrl'
            }
        }
    });

    // if none of the above states are matched, use this as the fallback
    $urlRouterProvider.otherwise('/tab/dash');
});

A few example links that you should put on your website look like this:

<a href="nikola://account">Open Account</a>
<a href="nikola://chats">Open Chats</a>
<a href="nikola://app/chats/4">Open chat detail 4</a>

I created a simple demo page so you can test this if you’d like and it’s here. Just open this link on your phone, after you’ve run the demo app on your phone and you should see the links working – meaning; taking you to proper sections in the app.

Just for reference, the contents of that file is this:

<!DOCTYPE html>
<html>
<head>
    <title>Deeplinking test</title>

    <style type="text/css">
        a {
            font-size: 80px;
            margin: 40px;
            display: block;
        }

        body {
            text-align: center;
        }
    </style>
</head>
<body>
    <a href="nikola://account">Open Account</a>

    <a href="nikola://chats">Open Chats</a>

    <a href="nikola://app/chats/4">Open chat detail 4</a>
</body>
</html>

The most important part here, on which I’ve wasted most of the time is the nikola://app/chats/4 part. Namely, at first I expected that you only should write it as nikola://chats/4, but by finding the bug report about this in the official repo I realized that you have to put something as a suffix (I’ve put app here).

Now, to run the app on your device, first prepare it:

ionic prepare ios

Then, open the platforms/ios/IonicDeeplinkTest.xcodeproj file:

And, make sure the string nikola (of course, change this string to your use case – usually the name of the app) is set as shown on the image:

For any potential additional info about setting up Xcode, check the official blog post.

Here’s the app in action, where you’ll notice how by clicking the link you are taken to the app to a specific screen. Also, additional nesting (opening up the detail screen of one ‘chat’) also works by passing in the argument.

Conclusion

I hope this was helpful and that it saved you some time in trying to figure this out.

In case you’re wondering how to do this for Ionic 2, 3+ apps, then it’s even easier as shown in the official docs.

How to use deep linking in Ionic 1 apps with Ionic Native Deeplinks plugin https://t.co/dAtt4Fwv3o

— Nikola Brežnjak (@HitmanHR) August 25, 2017

Ionic3

How to create icons and splash screen images automatically in Ionic framework 3

⚠️ In case you’re looking for how to create icons and splash screen images automatically in Ionic framework version 1, then you can check out this post. For more Ionic framework 3 posts check out the step by step info on How to get started with Ionic framework 3 on Windows and Mac.

TL;DR

Just execute ionic cordova resources command in your Ionic 3 project root folder.

!TL;DR

The icon is an important part of your application because it represents your application’s brand, and it helps to identify quickly where the app is on your phone. In case you’re familiar with creating apps then you will remember that it is a tedious process to create a lot of different size images both for iOS and Android platforms.

Also, the same goes for the so-called splash screen that shows up every time the application starts. Although having a splash screen is not mandatory, it certainly adds up to the feeling of a complete and professional application, which one would certainly want to convey with his application.

Ionic helps tremendously with this by providing a single Ionic CLI command to generate all the needed icon and splash screen sizes for us automatically. Also, Ionic created Photoshop Splash Screen Template, which you can download for free and use as a guideline for creating an icon. However, if you create you project by using ionic start command, as we have, then you’ll already have both the icon.png and splash.png files in the resources folder, and you can just edit them.

For when you’re creating a branded product having a custom made icon is definitely a must. However, in this case, I’ll show you how to use one of the free services to search for a free icon which you can then use in your application (even if your application is a commercial application).

I tend to use IconFinder a lot, and here are the settings which you have to use to filter out the calculator images that are Free (PRICE) and can be used in commercial applications and that don’t even require a link back (LICENSE TYPE).

Of course, you can also choose to buy an image if you happen to find one that you like. You can additionally search by format, size, and background. The filters should look like this (or use this prepared link which sets them automatically):

I’m going to use the last one in the first row. Simply click on it, and you should get to the download page that looks like this:

To download it just click on the green ‘Download PNG’ button.

Now, in the resources folder find and open the icon.png file with an image editor of your choice. I’m going to use Gimp in this example as it’s free cross-platform image editor available for all the major operating systems (Linux, OS X, Windows). You should see something like this:

Now we need to do a few steps:

  • select the whole area (Ctrl + a or Command + a) and press the DEL button on your keyboard. This should leave you with a blank white canvas.
  • drag the calculator icon on this white canvas and you should see something like this:
  • click on the scale tool
  • then click on the calculator image. You should get this popup:
  • change the values in the popup to 1024px for both Width and Height and click the Scale button.
  • click on the Alignment Tool, then click on the calculator image and then on the circled two buttons on the image below. This will align the calculator image horizontally and vertically

Now save the file by going to File->Overwrite icon.png:

Repeat the process to create the splash screen image and name it (overwrite it as) splash.png. The image I came up with looks like this:

Now that you have both icon.png and splash.png images ready navigate with your Terminal/Command prompt to the root folder of the application and run the following command:

ionic cordova resources

If at this point you get an error like this:

[ERROR] No platforms have been added. Please run: ionic cordova platform add

That means that you haven’t added any platforms yet to which Ionic should build. Since we’re going to build the app for both Apple Store and Android Play Store we’re going to use the following two commands:

ionic cordova platform add android
ionic cordova platform add ios

You should see an output similar to this:

ionic cordova platform add ios
> cordova platform add ios --save
✔ Running command - done!
Using cordova-fetch for cordova-ios@~4.4.0
Adding ios project...
Creating Cordova project for the iOS platform:
    Path: platforms/ios
    Package: io.ionic.starter
    Name: MyApp
iOS project created with [email protected]
Discovered plugin "cordova-plugin-console" in config.xml. Adding it to the project
Installing "cordova-plugin-console" for ios
Adding cordova-plugin-console to package.json
Saved plugin info for "cordova-plugin-console" to config.xml
Discovered plugin "cordova-plugin-device" in config.xml. Adding it to the project
Installing "cordova-plugin-device" for ios
Adding cordova-plugin-device to package.json
Saved plugin info for "cordova-plugin-device" to config.xml
Discovered plugin "cordova-plugin-splashscreen" in config.xml. Adding it to the project
Installing "cordova-plugin-splashscreen" for ios
Adding cordova-plugin-splashscreen to package.json
Saved plugin info for "cordova-plugin-splashscreen" to config.xml
Discovered plugin "cordova-plugin-statusbar" in config.xml. Adding it to the project
Installing "cordova-plugin-statusbar" for ios
Adding cordova-plugin-statusbar to package.json
Saved plugin info for "cordova-plugin-statusbar" to config.xml
Discovered plugin "cordova-plugin-whitelist" in config.xml. Adding it to the project
Installing "cordova-plugin-whitelist" for ios
Adding cordova-plugin-whitelist to package.json
Saved plugin info for "cordova-plugin-whitelist" to config.xml
Discovered plugin "ionic-plugin-keyboard" in config.xml. Adding it to the project
Installing "ionic-plugin-keyboard" for ios
Adding ionic-plugin-keyboard to package.json
Saved plugin info for "ionic-plugin-keyboard" to config.xml
--save flag or autosave detected
Saving ios@~4.4.0 into config.xml file ...
✔ Copying default image resources into ./resources/ios - done!

> cordova platform add android --save
✔ Running command - done!
Using cordova-fetch for cordova-android@~6.2.2
Adding android project...
Creating Cordova project for the Android platform:
    Path: platforms/android
    Package: io.ionic.starter
    Name: MyApp
    Activity: MainActivity
    Android target: android-25
Subproject Path: CordovaLib
Android project created with [email protected]
Installing "cordova-plugin-console" for android
Installing "cordova-plugin-device" for android
Installing "cordova-plugin-splashscreen" for android
Installing "cordova-plugin-statusbar" for android
Installing "cordova-plugin-whitelist" for android

               This plugin is only applicable for versions of cordova-android greater than 4.0. If you have a previous platform version, you do *not* need this plugin since the whitelist will be built in.

Installing "ionic-plugin-keyboard" for android
--save flag or autosave detected
Saving android@~6.2.3 into config.xml file ...
✔ Copying default image resources into ./resources/android - done!

After this, you can safely run the ionic cordova resources command, and you should get the following output:

✔ Collecting resource configuration and source images - done!
✔ Filtering out image resources that do not need regeneration - done!
✔ Uploading source images to prepare for transformations - done!
✔ Generating platform resources: 48 / 48 complete - done!
✔ Modifying config.xml to add new image resources - done!

From the output, you can see that 48 images were created and hopefully now you realize how much time this saved. All the needed configuration regarding the icons and splash screens was generated by Ionic and placed in the config.xml file.

It’s worth noting that you will not see the icon nor the splash screen when using the browser testing or Ionic View testing. Instead, you will only see these once you deploy them to the actual physical device or the emulator.

⚠️ You can add an iOS platform if you’re developing on a Windows machine, and ionic cordova resources command will generate icons and splash screens for it. However, keep in mind that you will not be able to build the project for iOS on your Windows machine. Instead, you’ll need a Mac computer to do so.

How to create icons and splash screen images automatically in Ionic framework 3 @ionicframework https://t.co/EKe3QDeo8E

— Nikola Brežnjak (@HitmanHR) August 23, 2017

Programming

Code Complete 2 – Steve McConnell – Working Classes

I just love Steve McConnell’s classic book Code Complete 2, and I recommend it to everyone in the Software ‘world’ who’s willing to progress and sharpen his skills.

Other blog posts in this series:

  • Part 1: Laying the Foundation
  • Chapter 5: Design in Construction

Class Foundations: Abstract Data Types (ADTs)

An abstract data type is a collection of data and operations that work on that data. The operations both describe the data to the rest of the program and allow the rest of the program to change the data.
For example, if you are not using ADTs to change a bold property of text you could write something like:

currentFont.bold = true;

but a better way, which keeps the abstraction of the class, is:

currentFont.SetBoldOn();

It ain’t abstract if you have to look at the underlying implementation to understand what’s going on. ~ P.J. Plauger

By thinking about ADT, and not about classes, we are programming into the language and not programming in language.

If a stack represents a set of employees, treat the ADT as employees rather than as a stack. Treat yourself to the highest possible level of abstraction.

Try to make the names of classes and access routines independent of how the data is stored.

For example:

game.save()

is better than

game.saveDataInFile()

One way to think of a class is as an abstract data type plus inheritance and polymorphism.

Good class interface

The first and probably most important step in creating a high-quality class is creating a good interface. This consists of creating a good abstraction for the interface to represent and ensuring the details remain hidden behind the abstraction.

If the class interface doesn’t present a consistent abstraction, then the class has poor cohesion.

Class interface consists of public routines (functions, methods)

Each class should implement one ADT!

In some cases, you’ll find that half a class’s routines work with half the class’s data, and half the routines work with the other half of the data. In such a case, you really have two classes masquerading as one. Break them up!

Move unrelated information to another class

Good Encapsulation

Encapsulation is a stronger concept than abstraction.
Abstraction helps to manage complexity by providing models that allow you to ignore implementation details. Encapsulation is the enforcer that prevents you from looking at the details even if you want to.

Don’t expose member data in public

You shouldn’t expose data like this:

float x;
float y;

because client code can monkey around with data. And perfect encapsulation would look like:

float GetX();
float GetY();
void SetX( float x );
void SetY( float y );

Now you have no idea whether the underlying implementation is in terms of floats x, y, whether the class is storing those items as doubles and converting them to floats, or whether the class is storing them on the moon and retrieving them from a satellite in outer space. ?

Avoid friend classes

In a few circumstances such as the State pattern, friend classes can be used in a disciplined way that contributes to managing complexity. But, in general, friend classes violate encapsulation. They expand the amount of code you have to think about at any one time, increasing complexity.

Design and Implementation Issues

Containment (“has a” Relationships)

Containment is a simple idea that a class contains a primitive data element or object. A lot more is written about inheritance than about containment, but that’s because inheritance is more tricky and error prone, not because it’s better.

Inheritance (“is a” Relationships)

Don’t inherit a class instead it’s a truly “is a” more specific version of the Base Class. ~ Barbara Liskov

Inherited routines come in three basic flavors:
+ An Abstract overridable routine means that the derived class inherits the routine’s interface but not its implementation.
+ Overridable routine means that the derived class inherits the routine’s interface but not its implementation

Overridable routine -> polymorphism
+ A non-overridable routine means that the derived class inherits the routine’s interface and its default implementation and it is not allowed to override the routine’s implementation

Don’t reuse names of non-overridable base-class routines in derived classes.

Avoid deep inheritance trees

Deep inheritance trees have been found to be significantly associated with increased fault rates. That’s because of deep inheritance trees increase complexity, which is exactly the opposite of what inheritance should be used to accomplish.

7±2 subclasses from base class, and maximum of three levels of inheritance

Rules for inheritance

  • If multiple classes share common data but not behavior, create a common object that those classes can contain.
  • If multiple classes share common behavior but not data, derive them from a common base class that defines the common routines.
  • If multiple classes share common data and behavior, inherit from a common base class that defines the common data and routines.
  • Inherit when you want the base class to control your interface; contain when you want to control your interface.

Law of Demeter

The rule states that Object A can call any of its own routines. If Object A instantiates an Object B, it can call any of Object B’s routines. But it should avoid calling routines on objects provided by Object B.

Constructors

Initialize all member data in all constructors, if possible. Initializing all data member in all constructors is an inexpensive defensive programming practice.

Enforce the singleton property by using a private constructor. If you want to define a class that allows only one object to be instantiated, you can enforce this by hiding all the constructors of the class and then providing a static GetInstance() routine to access the class’s single instance.

Deep and shallow copy

  • Deep copies are simpler to code and maintain than shallow copies
  • Shallow copies are created typically to improve performance

Prefer deep copies to shallow copies until proven otherwise.

Reasons to Create a Class

Create a class to hide information so that you won’t have to think about it and to reduce complexity. Sure, you will need to think about it when you write the class, but after it’s written you can forget about the implementation details and use the class without any knowledge of its internal workings.

Key points

  • Class interface should provide a consistent abstraction. Many problems arise from violating this single principle.
  • Classes must have data members and behavior
  • Containment is usually preferable to inheritance unless you’re modeling an “is a “relationship
  • Inheritance is a useful tool, but it adds complexity, which is counter to Software’s Primary Technical Imperative of managing complexity

My #notes from the classic Code Complete 2 #book by Steve McConnell chapter 5: Working Classes https://t.co/aWK3uWdBUr

— Nikola Brežnjak (@HitmanHR) August 21, 2017

Books

Flowers for Algernon – Daniel Keyes

My favorite quotes from the book Flowers for Algernon by Darren Hardy which I rated 5/5 on my Goodreads account. Lately, I haven’t been reading any nonfiction books, but this was a great one to start with again.

Now I understand that one of the important reasons for going to college and getting an education is to learn that the things you’ve believed in all your life aren’t true, and that nothing is what it appears to be.

It’s easy to make friends if you let people laugh at you.

Intelligence is one of the greatest human gifts. But all too often a search for knowledge drives out the search for love. This is something else I’ve discovered for myself very recently. I present it to you as a hypothesis: Intelligence without the ability to give and receive affection leads to mental and moral breakdown, to neurosis, and possibly even psychosis. And I say that the mind absorbed in and involved in itself as a self-centered end, to the exclusion of human relationships, can only lead to violence and pain.

A child may not know how to feed itself, or what to eat, yet it knows hunger.

Strange about learning; the farther I go the more I see that I never knew even existed. A short while ago I foolishly thought I could learn everything – all the knowledge in the world. Now I hope only to be able to know of its existence, and to understand one grain of it. Is there time?

There are a lot of people who will give money or materials, but very few who will give time and affection.

No one really starts anything new, Mrs. Nemur. Everyone builds on other men’s failures. There is nothing really original in science. What each man contributes to the sum of knowledge is what counts.

 …the men of the cave would say of him that up he went and down he came without his eyes.

My favorite #quotes from the #book Flowers for Algernon by Daniel Keyes https://t.co/D37Vesvknj

— Nikola Brežnjak (@HitmanHR) August 21, 2017

Ionic3

How to make money with Google AdMob ads in Ionic framework 3

In case you’re looking for a way to implement Google AdMob ads in Ionic framework 1, then check out this tutorial: Adding AdMob to Ionic framework application step by step.

Introduction

There are multiple ways you can earn money with your app these days and here are just a few of them:

  • Paid app – set a price for your app directly on the App/Play Store that users need to pay before downloading your app
  • Freemium – give the app for free but charge for in-app purchases like adding some extra features (think more gold or faster production in game apps)
  • Ad-based – show ads inside your application. Potentially offer the in-app purchase to remove the ads

In this post, I’m going to cover the Ad-based monetization option, and I’ll show you how to add Google AdMob ads to a simple Ionic 3 blank template application. There are two parts to implementing Google AdMob ads to an Ionic project and I broke them into: AdMob settings and Ionic settings.

Demo app and repo

You can check the final code on Github. When you clone it, run npm install inside the project. In case you have your development machine set up for Ionic, then you can run the project with ionic emulate ios or ionic emulate android. If you don’t but would like to, see this post on How to get started with Ionic framework 3 on Mac and Windows.

You should see something like this in your simulator/emulator:

AdMob settings

Let’s start with AdMob settings:

  1. Sign in/Sign up for AdMob at https://www.google.com/admob/
  2. Click the Apps and then ADD APP button:

  1. Since our app is not published yet we will click the No button:

  1. Fill in the app name and platform and click the ADD button:

  1. Save the App ID somewhere and proceed to create the Ad unit

  1. Select Banner Ad format:

  1. Configure the adds type, size, placement, style, name:

  1. You can read additional info on how to implement GA and AdMob, but for now let’s just click Done:

  1. You will now see the following similar screen:

The most important thing to note here is this Ad unit ID, which in my test case is ca-app-pub-7957971173858308/5068937357. Make a note of this string as it’s the most important part of this setting. You can click on the copy to clipboard button and paste it as a comment (for now) in your app.

  1. Create as much Ad units as you may need (for each platform[iOS, Android] and ad format [Banner, Interstitial, etc.]). In my case, I just created the additional Interstitial Ad and will use them on both iOS and Android devices for the purpose of this demo.

Ionic settings

Those of you familiar with Ionic 1 know that you can add any plugin to your Ionic project thanks to the project called ngCordova. For Ionic 3, there’s the same thing called Ionic Native.

Ionic Native is a TypeScript wrapper for Cordova/PhoneGap plugins that makes it easy to add any native functionality that you may need into your Ionic app. Ionic Native wraps the plugin callbacks in a Promise or an Observable, providing a common interface for all plugins and ensuring that native events trigger change detection in Angular.

First, let’s start an empty Ionic 3 application based on the blank template:

ionic start Ionic3AdMobTest blank --cordova

You should see the following output:

✔ Creating directory ./Ionic3AdMobTest - done!
[INFO] Fetching app base (https://github.com/ionic-team/ionic2-app-base/archive/master.tar.gz)
✔ Downloading - done!
[INFO] Fetching starter template blank (https://github.com/ionic-team/ionic2-starter-blank/archive/master.tar.gz)
✔ Downloading - done!
✔ Updating package.json with app details - done!
✔ Creating configuration file ionic.config.json - done!
[INFO] Installing dependencies may take several minutes!
> npm install
✔ Running command - done!
> npm install --save-dev --save-exact ionic@latest
✔ Running command - done!
> npm install --save-dev --save-exact @ionic/cli-plugin-ionic-angular@latest
✔ Running command - done!
> npm install --save-dev --save-exact @ionic/cli-plugin-cordova@latest
✔ Running command - done!
> npm dedupe
✔ Running command - done!
> git init
✔ Running command - done!
> git add -A
✔ Running command - done!
> git commit -m "Initial commit" --no-gpg-sign
✔ Running command - done!

♬ ♫ ♬ ♫  Your Ionic app is ready to go! ♬ ♫ ♬ ♫

Run your app in the browser (great for initial development):
  ionic serve

Run on a device or simulator:
  ionic cordova run ios

Test and share your app on a device with the Ionic View app:
  http://view.ionic.io


Next Steps:
Go to your newly created project: cd ./Ionic3AdMobTest

Navigate to the root of the application with your Terminal/Command prompt and execute the following command to add the cordova-plugin-admobpro plugin:

ionic cordova plugin add cordova-plugin-admobpro

You should see the following output after running the command:

✔ Running command - done!
Adding cordova-plugin-admobpro to package.json
Saved plugin info for "cordova-plugin-admobpro" to config.xml

Additionally, you will also need to run this command:

npm install --save @ionic-native/admob-pro

which, if completed successfully, will only output something like added 1 package in 3.331s to the console.

You need to add this second command as well because this installs some files needed by TypeScript.

⚠️ At this point, depending on the version of Ionic CLI that you have you may need to add a platform by executing: ionic cordova platform add ios or ionic cordova platform add android depending on the platform you’re trying to build for. You have to execute that if the command ionic cordova platform ls shows that you don’t have any installed platforms on the current project:

→ ionic cordova platform ls
✔ cordova platform ls - done!
Installed platforms:
Available platforms: 
 android ~6.2.2
 blackberry10 ~3.8.0 (deprecated)
 browser ~4.1.0
 ios 4.4.0
 osx ~4.0.1
 webos ~3.7.0

If everything is fine with running ionic cordova platform add ios you will see an output like this:

Using cordova-fetch for cordova-ios@~4.4.0
Adding ios project...
Creating Cordova project for the iOS platform:
    Path: platforms/ios
    Package: io.ionic.starter
    Name: MyApp
iOS project created with [email protected]
Installing "cordova-plugin-admobpro" for ios
Installing "cordova-plugin-extension" for ios
Discovered plugin "cordova-plugin-console" in config.xml. Adding it to > the project
Installing "cordova-plugin-console" for ios
Adding cordova-plugin-console to package.json
Saved plugin info for "cordova-plugin-console" to config.xml
Discovered plugin "cordova-plugin-device" in config.xml. Adding it to > the project
Installing "cordova-plugin-device" for ios
Adding cordova-plugin-device to package.json
Saved plugin info for "cordova-plugin-device" to config.xml
Discovered plugin "cordova-plugin-splashscreen" in config.xml. Adding > it to the project
Installing "cordova-plugin-splashscreen" for ios
Adding cordova-plugin-splashscreen to package.json
Saved plugin info for "cordova-plugin-splashscreen" to config.xml
Discovered plugin "cordova-plugin-statusbar" in config.xml. Adding it > to the project
Installing "cordova-plugin-statusbar" for ios
Adding cordova-plugin-statusbar to package.json
Saved plugin info for "cordova-plugin-statusbar" to config.xml
Discovered plugin "cordova-plugin-whitelist" in config.xml. Adding it > to the project
Installing "cordova-plugin-whitelist" for ios
Adding cordova-plugin-whitelist to package.json
Saved plugin info for "cordova-plugin-whitelist" to config.xml
Discovered plugin "ionic-plugin-keyboard" in config.xml. Adding it to > the project
Installing "ionic-plugin-keyboard" for ios
Adding ionic-plugin-keyboard to package.json
Saved plugin info for "ionic-plugin-keyboard" to config.xml
--save flag or autosave detected
Saving ios@~4.4.0 into config.xml file ...
✔ Copying default image resources into ./resources/ios - done!

Btw, there is also a free version of the AdMob Pro plugin. But, honestly, if you really start making money with your app, this will be a minor expense. Besides, if you’re having problems giving back to the actual plugin through which you’re making money then my dear padawan you have yet much to learn…

Now, let’s add this plugin to our app’s NgModule. In the src/app/app.module.ts file import AdmobPro:

import { AdMobPro } from '@ionic-native/admob-pro';

and then add it to the Providers array. The whole contents of the src/app/app.module.ts file should now looks like this:

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { AdMobPro } from '@ionic-native/admob-pro';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';

@NgModule({
    declarations: [
        MyApp,
        HomePage
    ],
    imports: [
        BrowserModule,
        IonicModule.forRoot(MyApp)
    ],
    bootstrap: [IonicApp],
    entryComponents: [
        MyApp,
        HomePage
    ],
    providers: [
        StatusBar,
        SplashScreen,
        AdMobPro,
        { provide: ErrorHandler, useClass: IonicErrorHandler }
    ]
})
export class AppModule { }

Now I’m going to show you the final content of the src/pages/home/home.ts file and will explain what was changed step by step:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';

import { AdMobPro } from '@ionic-native/admob-pro';
import { Platform } from 'ionic-angular';

@Component({
    selector: 'page-home',
    templateUrl: 'home.html'
})
export class HomePage {
    constructor(public navCtrl: NavController, platform: Platform, private admob: AdMobPro) {
        platform.ready().then(() => {
            var admobid = {
                banner: 'ca-app-pub-7957971173858308/5068937357',
                interstitial: 'ca-app-pub-7957971173858308/5667703151'
            };

            this.admob.createBanner({
                adId: admobid.banner,
                isTesting: true,
                autoShow: true,
                position: this.admob.AD_POSITION.BOTTOM_CENTER
            })

            this.admob.prepareInterstitial({
                adId: admobid.interstitial,
                isTesting: true,
                autoShow: false
            })
        });
    }

    showInterstitialAd() {
        if (AdMobPro) {
            this.admob.showInterstitial();
        }
    }
}

First, we added the imports:

import { AdMobPro } from '@ionic-native/admob-pro';
import { Platform } from 'ionic-angular';

Then, via the constructor we injected Platform and AdMobPro:

constructor(public navCtrl: NavController, platform: Platform, private admob: AdMobPro)

Then we wrapped everything in the platform.ready() promise. This is the most important part of the code! If you wouldn’t do that, it could happen that your app would start up and the plugins would still not be properly set up, and you wouldn’t see the ads displayed.

But then again, sometimes you would, and this is what it would make it a nightmare to debug. This is a very common issue that I’ve seen even back from Ionic 1 when answering the questions on StackOverflow. So, you may want to keep an ?️ on the fact that you need to wrap any plugin calls inside the platform.ready() promise, as that way you’ll be sure that all of the plugins have loaded before you’ll use them.

The code that’s executed after the promise resolves sets up our admobid object with banner and interstitial properties. Then we’re calling the createBanner and prepareInterstitial functions on the injected admob object. Note how the banner is set to show automatically when the app loads (autoShow: true) and the interstitial isn’t. Also, note how we’ve set the position of the banner ad to the bottom:

platform.ready().then(() => {
    var admobid = {
        banner: 'ca-app-pub-7957971173858308/5068937357',
        interstitial: 'ca-app-pub-7957971173858308/5667703151'
    };

    this.admob.createBanner({
        adId: admobid.banner,
        isTesting: true,
        autoShow: true,
        position: this.admob.AD_POSITION.BOTTOM_CENTER
    })

    this.admob.prepareInterstitial({
        adId: admobid.interstitial,
        isTesting: true,
        autoShow: false
    })
});

Then we added the showshowInterstitialAdAd function, which shows the Interstitial ad:

showInterstitialAd() {
    if (AdMobPro) {
        this.admob.showInterstitial();
    }
}

Of course, at this point, change the admobid object properties to your AdMob keys which you obtained in the first part (step 9).

? If you run into any problems with this, just ping in the comments, and I’ll do my best to help you.

One common thing that you might have to do for Android is to install some extras via the Android SDK manager. To do so open Android Studio and select Configure->SDK Manager:

Make sure you have installed the packages marked as Installed on the image below (usually, those are Google Billing Library and Google Play services):

What kind of an ad should you show?

This plugin’s documentation states an interesting fact that it’s strongly recommended to use the Interstitial ad type because it brings more than 10 times profit than the banner Ad. Here’s the table from the official documentation:

Ad Format Banner Interstitial
Click Rate < 1% 3-15%
RPM (1k impressions) 0.5$ – 4$ 10-50$

Banner ad is the small add that is usually placed in the bottom of the screen, whereas Interstitial ads are full-screen ads that cover the interface of their host app. Therefore, you may rather want to opt for this kind of an ad instead for the Banner one.

It’s important to note that there’s probably no exact formula here on when to show the Interstitial Ad, but there are some best practices, and this is what Google has to say about it:

Interstitial ads work best in apps with natural transition points. The conclusion of a task within an app, like sharing an image or completing a game level, creates such a point. Because the user is expecting a break in the action, it’s easy to present an interstitial without disrupting their experience. Make sure you consider at which points in your app’s workflow you’ll display interstitials, and how the user is likely to respond.

You can learn a bit more about it here.

Anyways, as you saw in our demo code above, we opted for showing the banner ad all the time in the bottom of the screen, and we’re showing the interstitial ad when we click the button. You can tweak this any way you like in your app, but please remember the points mentioned above about best practices.

Finally, the view template

Replace the src/pages/home/home.html file content with this:

<ion-header>
    <ion-navbar>
        <ion-title>
            Ionic Blank
        </ion-title>
    </ion-navbar>
</ion-header>

<ion-content padding>
    <button ion-button (click)="showInterstitialAd()">Show the interstitial Ad</button>
</ion-content>

Clicking the Show the interstitial Ad button will call the showInterstitialAd() function which will show the interstitial as shown in a gif at the beginning of this tutorial.

Let’s test this

Run ionic cordova emulate ios and in a few seconds your simulator should start up and show you this:

There are few things that could go wrong with this, so for example if you get an error like this:

Error: Cannot read property 'replace' of undefined

[ERROR] Cordova encountered an error.
        You may get more insight by running the Cordova command above directly.

[ERROR] An error occurred while running cordova run ios (exit code 1).

Then you can try the solution from this StackOverflow post: Execute the following command in the platforms/ios/cordova/node_modules folder: sudo npm install ios-sim@lates.

If you get an error telling you that it can’t open up the simulator, then you may want to try running the build command: ionic cordova build ios and after that open the MyApp.xcworkspace file from the platforms/ios folder:

Select the simulator device that you’d like to run your app on and click the run button from your Xcode:

If you run into any other problems while trying to run this, let’s try to solve them in the comments and thus help others that may have the same issues as well ?

Conclusion

In this post, we’ve shown how easy it is to add monetization options to your app. Now it’s ‘just’ on you to actually make an app that will be used a lot of times and whose users will want to click on the ads. ?

Till next time, ✌️

How to make #money with Google #AdMob ads in #Ionic framework 3 https://t.co/In5UPrLTyn

— Nikola Brežnjak (@HitmanHR) August 16, 2017

Page 17 of 51« First...10«16171819»203040...Last »

Recent posts

  • Discipline is also a talent
  • Play for the fun of it
  • The importance of failing
  • A fresh start
  • Perseverance

Categories

  • Android (3)
  • Books (114)
    • Programming (22)
  • CodeProject (35)
  • Daily Thoughts (77)
  • Go (3)
  • iOS (5)
  • JavaScript (127)
    • Angular (4)
    • Angular 2 (3)
    • Ionic (61)
    • Ionic2 (2)
    • Ionic3 (8)
    • MEAN (3)
    • NodeJS (27)
    • Phaser (1)
    • React (1)
    • Three.js (1)
    • Vue.js (2)
  • Leadership (1)
  • Meetups (8)
  • Miscellaneou$ (77)
    • Breaking News (8)
    • CodeSchool (2)
    • Hacker Games (3)
    • Pluralsight (7)
    • Projects (2)
    • Sublime Text (2)
  • PHP (6)
  • Quick tips (40)
  • Servers (8)
    • Heroku (1)
    • Linux (3)
  • Stack Overflow (81)
  • Unity3D (9)
  • Windows (8)
    • C# (2)
    • WPF (3)
  • Wordpress (2)

"There's no short-term solution for a long-term result." ~ Greg Plitt

"Everything around you that you call life was made up by people that were no smarter than you." ~ S. Jobs

"Hard work beats talent when talent doesn't work hard." ~ Tim Notke

© since 2016 - Nikola Brežnjak