Rapture in Venice

:: Mobile Design and Development Shop specializing in iPhone, iPad, and Android

Developing a Local CocoaPod Alongside Your iOS App

Whether you’re an independent developer or a large team building apps for multiple clients, there’s no better way to improve your efficiency than by collecting your most useful code into one re-usable, unit-tested framework.

Today, I’m going to show you a great way to use CocoaPods so that you can simultaneously build your Swift apps while locating generalized solutions into their own standalone Pod.

Let’s Create Our Pod

The first thing we’ll do is create a Pod that will store all of our handiest classes, components, and extensions.  We start by creating a project for a Swift framework:

Choose "Cocoa Touch Framework"

Choose “Cocoa Touch Framework”

Name it however you like and make sure it uses the Swift language.  I’ll call mine Tilikum:

Call the framework whatever you like, but I'll dedicate mine for the poor, imprisoned Orca. :-(

I’ll dedicate my new Pod to that poor, imprisoned Orca named Tilikum. :-(

Now, let’s write ourselves a useful little extension for the String class for this example. Obviously, a lot more work will go into this later, but for the purpose of this tutorial we’ll simply get things started with something small.  Here’s our code:

A little extension to get us started...

A little extension to get us started…

This extension adds a length property to the String class.  We’ll add more down the road, but this is enough to demonstrate our Pod setup.  If you’ve never written a framework before, be sure to mark the extension as public, too, or else the code can’t be accessed outside of it!

OK, the extension is done, but we’re not ready to move on to our application until we write some unit tests first!  Remember, the best part about centralizing all this code is that it allows us to properly unit test in an organized manner.  You’d never copy the unit tests around along with the code…go on, admit it.

Let’s add the tests:

By adding unit tests we not only keep the code in one place, we pledge to harden it with unit tests!

By adding unit tests we not only keep the code in one place, we pledge to harden it with unit tests!

Alright, the code is all done, so now we need to turn this project into a bonafide Pod. Assuming you have CocoaPods installed, let’s hit the command line with this to create a default Podspec

This will create a starter Podspec for us.

This will create a starter Podspec for us.

Make whatever changes you like to the default file, but do make sure you change the source_files value to something like this:

s.source_files  = “Classes”, “Tilikum/*.swift”

Build and voila! Congratulations, your Pod is ready to go!  Next, we’ll create an app that uses it.

Let’s Create Our App

With the Pod created, we can have as many apps as we want using it…but let’s start with just one.  Once again, we create a new project, this time a regular ole iOS Swift app:

This TilikumClientApp app will use the useful code we keep in the Tilikum Pod.

This TilikumClientApp app will use the Tilikum Pod.

Note that there’s no requirement that we share a workspace or working directory or anything with the Pod.  The Pod is completely independent of our app. The only requirement, obviously, is that our app uses Cocoapods so that it can access Tilikum. Let’s configure TilikumClientApp now.

First, we integrate Cocoapods and create our Podfile:

This created the default Podfile.

This command creates the default Podfile.

Now, the important part: we add our Tilikum Pod to the project.  Usually we’d do this by adding a pod specification found in the main Cocoapods repository, but this time we’re going to point it at a local path:

Point to your local Pod and don't forget to add the use_frameworks! method or your Swift framework won't be linked in properly.

Point to your local Pod and don’t forget to add the use_frameworks! method or your Swift framework won’t be linked in properly.

This syntax points to the Tilikum Pod that’s located in the same parent directory as the app.  If you want to store it elsewhere, you can, but if you’re working with multiple developers make sure you agree on a hierarchy that works for everybody.

There are many benefits to pointing at a local directory rather than an online repository:

  • You can freely add new code to the Pod while you’re working on the app.
  • You don’t have to commit or push any code and you certainly don’t need to cut a new version of your Pod for every single change you make.
  • This encourages centralizing your generalized code in the Pod due to a quick development cycle.

Right now you might be thinking that relying on a current snapshot of the code as it stands in a developer’s local workspace isn’t a good thing, and you’re right!  You’re also reading the wrong tutorial. ;-)  This is about developing your Pod and app at the same time, not using versioned Pods.  You’ll have to commit and version things while you code your app to do that and that’s not going to motivate you to maintain both at the same time. You’re have to rely on generalizing and exporting your code to a Pod after your development is done and…well…we all know that’s likely never going to happen.  You’ll be too busy celebrating your new app’s release to think about it.

This method works really well and, obviously, is ideal in single-developer environments.

Alright, where were we?  Ah yes!  Now, we just need to use the Tilikum Pod in our app. The code is simple, we merely need to import the framework where we need it and then just use our new extension however we wish:

We import Tilikum above and then we can simply use our new length method!

We import Tilikum above and then we have access to our new length method!

Done!  Now, as we develop our app more, whenever we want to add some useful code we think our other apps will want to leverage, we add it to Tilikum rather than the app. Instant re-usability! Cool!

There’s just one little wrinkle we need to talk about (you knew there would be), and that’s what we need to do to refresh the Pod once we’ve added something to it.

Let’s Update the Pod so the App Sees New Stuff

Let’s say we add a new method to our extension, or a new extension altogether. We build Tilikum, but when we try to build TilikumClientApp we’ll find that the new code can’t be used yet.

Why?

The answer is simple: the Pod has to be updated in the app. Remember, TilikumClientApp isn’t pointing directly at Tilikum, it’s simply installed the Pod into its workspace just as if it was in the main Cocoapods repository.  So, we need to re-install it.

Doing that isn’t so intuitive, however.  Running pod install again won’t work because Cocoapods won’t think that Tilikum is updated even if you committed the new code.  You can update the Podspec’s version number, but we don’t want to do that after every code change or we’ll quickly stop bothering with the Pod altogether.  Luckily, there’s an easy command line sequence that takes care of it for us:

Don't worry that the other Pod (iRate) appears to be getting re-downloaded, it's actually cached.

Don’t worry that the other Pod (iRate) appears to be getting re-downloaded. It’s actually cached.

Boom!  Keep in mind, with the command above, that this will re-install all the Pods we have specified in our Podfile. However, the other Pods are actually cached and aren’t really being downloaded at all. (On Mac, you can find the cached Pods in ~/Library/Caches/Cocoapods/Pods.)  This means we have very little overheard to this step and it’s therefore feasible to do.

Summary

Using this setup, you can easily locate your app-specific code in your app while locating your re-usable code in the Pod.  You can build quick and easy and won’t have to do any refactoring later.  As a bonus, keeping our most useful code in one place encourages us to build unit tests to make sure they’re correct!

Now, all our handy String extensions and enhanced UIButton classes are instantly available to our other apps without having to pull them out later.  It’s all done as we go!

When you’re done building a particular version of your app, you can go ahead and check in the Pod code and cut a new version of it if you like (if you’ve made it public).  Other users can then use your Pod the normal way.

Awesome?  Awesome!

 

  • Print
  • Facebook
  • Twitter
permalink

Apple Watch Impressions: A Day Without My iPhone

I received my Apple Watch yesterday after an arduous, never-ending 10-day wait that I wouldn’t wish on my worst enemy. Since the March announcement, I have intentionally kept myself in the dark on the details of this new Apple product. Does it have a microphone? I didn’t know. Can you make calls on it? I really had no idea. Had I seen one? Nope, I’ve intentionally avoided seeing, touching, or ogling any real-life version of the gadget!

Last night I got it and marveled at its clear screen and completely new UI. I spent quite a bit of time tinkering with the configuration and learning more and more about it. Today, I decided to try and go the whole day without my iPhone. I plunked my beloved phone in my desk drawer and am ready to take the day on armed with just my Watch.

Here we go.

8:55 AM – I have a conference call with my client. I’m trying to do it with my Watch since you can make and receive calls with the Watch via the iPhone. Unfortunately, there’s no dialer, so I’ve had to grab the phone and dial the number (and hang up) so it’s not on my recent calls list. (I’ll add it to my Contact list later.)

8:58 AM – I can’t dial the passcode either. :-/ Gonna grab my phone again and dial it in on there so I can continue on the Watch.

8:59 AM – Oh my gods, once I did that on the phone, the watch no longer has the audio. I don’t know how to “pass the call” back over to it. Ugg. Alright, that will require some research later.

9:17 AM – I just noticed Nick Arnott has his Watch, too.  I can draw a picture to him using something they call “Digital Touch.”  I drew him a dick. (Obviously.)

10:02 AM – Texting with my brother. There’s no keyboard but the microphone does a good job of translating. The lack of capitalization and grammar, as always, does bug me. But it’s a text. And it’s way better than his Android phone transliterates anyway.

11:10 AM – Playing voicemails is odd.  Watch has a mechanism where it turns on when you hold your wrist up.  It seems to be able to tell via the accelerometer, but it does get it wrong a lot, too. (It turns on in many situations I don’t need it to.)  If I go to play a voicemail and want to continue listening while I type, though, Watch just cuts it off and turns off.  Not what I want it to do.  Am I supposed to stand there looking at my watch like an idiot?

12:10 PM – Asked Siri what movies were playing for my wife.  Gave me a nice list.  The Digital Crown is a nice scrolling mechanism so you don’t have to cover all the content with your fat finger.  Reminds me of the Blackberry days, honestly. :-)

12:49 PM – Figured out how to get the Mickey Mouse face watch. Soooooo coooooool.  I love the animation.  I plan on doing a Disneyland Ride Time app this month, so this matches wonderfully.

IMG_6208

12:50 PM – Used my phone take a pic of my wrist.  Surely, that doesn’t count…

12:58 PM – Power is down to 16% so charging for the first time.  The magnetized charger is cool because there’s no port you have to expose, but you do have to place the Watch down carefully so it doesn’t come off.  The cord is also HUUUGE. I didn’t charge it last night…and I can’t recall what the charge was when I got it, so I wouldn’t draw an inference.  Let’s see how long it takes to charge all the way now…

2:25 PM – My wrist feels so naked…

2:33 PM – Fully charged!  So, I’d guess it takes about 2 hours to go from 0% to 100%.  Not terrible.  That includes not putting the Watch in Airplane Mode (as I tend to do with the iPhone).

2:51 PM – A lot of apps that have a good idea of how to present themselves on the Watch. I like the Dictionary app which basically gives you a Word of the Day and lets you speak a word.  It’s not Siri integration, just an API to get spoken words, of course.

2:53 PM – Still can’t figure out how to hand off a call from the iPhone to the Watch.  Sad.

3:13 PM – Just said “Hey Siri” while three apps were downloading.  Watch froze. Uh oh. Just unfroze now after the apps finished.  That’s a bug.

3:25 PM – Downloaded a few recommended Watch apps.  Rules looks like my kind of puzzle game, so that’s an easy one.  Shazam I grabbed because the integration with Siri isn’t there on the Watch, only the phone. (Not sure why not.)  Also grabbed a calculator so I can nerd out like the old days with my calculator watches!  Ooh, I wonder if there’s a calculator face for this…?

3:28 PM – You’ll channel your inner OCD when you look at the apps on your Watch and try to get them to form a perfect geometric shape.  I added 3 new apps, but had to add a 4th to keep things symmetrical.  It was Instapaper.  I do wonder why they won’t let you hide the Stocks app?  What’s the hurt?  I think the Apple employees are all heavy investors.

3:30 PM – Whoa!  When you read something you saved in InstaPaper, it speaks it rather than show it in printed form.  Not sure how I feel about it, but that was a surprise!  It’s playing my iPhone, though, not the watch. It’s seem that Watch only plays small sound effects, not music, but Voicemails will play on it. So…

3:35 PM – OK, if turn off “Wrist Raise” in the Watch settings you can let your voicemails play while you type and stuff because the device won’t turn off.  Of course, it doesn’t turn off now.  If you cover the face with your palm it does, which is an intuitive thing.  But gone are the days of raising your wrist to look at the time.  Not sure which I prefer.  Wish Watch was just smart enough to not turn off when you’re listening to audio.

3:50 PM – Rules! is awesome.  Still disappointed Instapaper audio won’t play on the Watch.  Why on Earth would I want to use my Watch as a remote control to play the audio on my iPhone?  I’ve never made Instapaper on the phone play audio…I didn’t even know you could.

4:55 PM – Did a workout and decided to keep the watch on to see the effect.  Felt smooth. The sport version really is extremely comfortable.  Taking it off it was sweaty on the inside.  I’d recommend taking it off during workout if you can.  You just want to avoid any wetness on a $400 watch when ya can, ya know?

6:00PM – Keeping track of the Mets, Rangers, and Yankees games.  The watch is great for doing this without keeping the phone on ya.  I’ve loved asking Siri for sports scores since I got my iPhone 5.  I love the score display, too.

8:15 PM – Well, the day is coming to a close.  Overall, I rate the Watch a 7/10. It’s most certainly an item of pure convenience, but its utility isn’t fully realized yet.  Many apps have some limited functionality and I think that’s how Watch apps should be, but that functionality has to be useful.

The MLB app has a Glance (think a screen on your lock view) that shows scores for your favorite teams.  It doesn’t show “featured” games, though.  I think it should be on the list…I have to scroll anyway.  I can also control game audio, but the audio should be on my WATCH, not the phone.  That’s why I’m wearing it.

I love the Force Touch.  This is the gesture where you press a little harder than a tap. Apple encourages this use in Watch apps, and several do use it.  I expect to see it more. (Maybe to get a phone call to handoff to Watch?!)

Still, it will take many months before the apps catch up to what the Watch can do.  If you don’t have one, the only reason you should get one now is as a developer.  As a consumer, you can definitely wait.  Maybe even until the second iteration of the hardware when the Watch is thinner. (It’s a tad bit bulky now.)

A fun experience.  It’s like when the iPhone was released. A whole new world and my creative juices are flowing to make something cool!

  • Print
  • Facebook
  • Twitter
permalink

, ,

The Horrors of Sub-Contracting

I’ve been a full-time mobile consultant now for three years and, even in that short period of time, I’ve seen the kind of work I do change a lot. When I started, I did a lot of projects for budding entrepreneurs and fixed-bid work. I loved it! But over time, partly due to the allure of a high (and steadily rising) hourly rate and the cold reality of an App Store not making many millionaires anymore, I’ve found myself doing a lot more corporate work.

Contract work with big businesses comes in a couple of forms: hourly work and sub-contracting. Hourly work is usually working on a company’s app product line or creating a new app for them. Sub-contracting is the kind of work you get through bigger mobile shops who need to augment a project team with a mobile specialist like I am.

One such gig has made my 2014 a nightmare and I wanted to share that experience with you.

The Opportunity

Back in October 2013, I was approached by a former co-worker of mine about re-writing a pair of mobile apps (iOS and Android) for a company he was working with. This is on behalf of a local Denver firm I won’t name. The apps were small and simple and I had some time available so I told him I’d do it. I bid on the project, but 2 months later the project still wasn’t ready to go and I had to back out because my schedule had filled up by then.

A month later and things were just then ready to go and…hating to pass up paying work…I decided to go ahead and do it. There were some warning signs, namely the length of time it took to start the project, but I really wanted to re-write these apps.

I should have said no.

Turbulance on Takeoff

It’s now January 6th, almost 3 months since I was approached about the project, and here we were at the kickoff meeting. So nice to get this going! Now, my contract reflected the expected timeline; this app would be done by the end of March. Unfortunately, this is also when I learned that neither design nor services had been started yet. What was going on during those previous 3 months??

I had cleared 2 full weeks in my schedule to get things going, but I was going to have very little to do. I toyed with app architecture and tried to make what I could of the early wireframes. A couple services popped up, but it was not even a week in before I was already stalled. I’d started with the Android app, and wanted to complete it before moving on to iOS, but to heck with it…I decided to start the iOS app while I waited.

Soon thereafter, I abandoned working on the apps full-time and would come back to them when the services and designs gave me more to do. I’d pop in for a day or two, a night here, a couple hours there. I’d be working on the apps twice a week, then once a week, then not at all for weeks, then a few times a week, then not at all again.

I’m ashamed to say, I am still to this day working on this app. It’s September 18th, over 8 months since it began.

The Fundamental Problem

I’m an experienced consultant. I’d done part-time consulting for YEARS before I went full-time. I know how to handle a client, so what went wrong? Why is this project taking so long? More alarmingly, the iOS app has long been abandoned! I’m still trying to finish just the Android app! (I stripped out the iOS version of the app from my contract and it won’t be done by me. All the hours spent on the iOS version were wasted.)

What went wrong? What. Went. Wrong?

The problem is my client isn’t a client. They’re a firm, and they’re the ones working directly with the client. I have no managerial responsibility on this project, I am simply the “mobile guy.” And for reasons I cannot even begin to fathom, this firm has allowed their client to drag a simple project out for 8 months:

  • The client spends weeks before they’ll make a decision on…anything.
  • The client approves wireframes and then, later, wants wholesale changes.
  • The client wanted a totally new app design, but slowly reverted every design decision back to the “way it is now.”
  • The client doesn’t answer questions until they’re asked 12 times.
  • The client is clearly insane.

      None of these things are new to me. I’ve worked with dozens of clients, and I’ve had to fire a few of them for being, quite frankly, shitty clients. Once, I fired a client because they had just fired their designer for the second time and they wanted a whole new app design (for a second time). I told them to keep their 2nd payment and I’m done. Another time, I ran away from a client because I had 10 different roles and had been given no authority for any of them.

      Bad clients happen. So what’s the problem in this case? The problem is…it’s not my client. I’m at the mercy of a firm that has mishandled the client in every way imaginable. They’ve allowed the project to stretch to absurd lengths. They’ve allowed them to constantly change requirements with no change to the contract. Quite frankly, the PM can’t manage this project.

      Why am I still working on it? Because I’m loyal to a fault. Also an idiot.

      Outcome

      Loyalty has its limits, and I’ve just now informed my friend that I will cease all development next week, whether the app is published or not. I didn’t want it to come to this, but despite the app being all but done I still think things would drag into 2015. What a bummer New Year’s woulda been. I think I’ve been way, way, way more than fair in all this. I don’t think more than one or two of my contractor buddies would still be working on this thing. And what’s frustrating is it’s not a project that’s a tech failure, or that it’s too buggy, or that it’s just taking too long. No, it’s simply that the client has no idea what they want, they have no urgency to get this app done, and I’m powerless to effect them as a sub-contractor.

      Let this tale be a warning about the horrors of sub-contracting.

      • Print
      • Facebook
      • Twitter
      permalink

      ,

Getting the Charles Debugging Proxy to Work with iOS and Genymotion

The Charles Web Debugging Proxy is a near mandatory tool for any developer. Getting it to work with your desktop apps is usually very easy since it just…works. However, for use with mobile, there are a couple tricks.

Configuring for the iOS Simulator

The iOS Simulator is a native app that “imitates” your iTouch device. Because of that, the only configuration you need is to install the Charles certificate. You can do that by downloading the cert and just dragging it on to your iOS Simulator. (There’s no Windows configuration for this because you can’t run the iOS simulator on windows.)

Restart Charles and your simulator to be assured of a working connection.

Configuring for Genymotion/Android

We’re going to ignore the official Android emulator because you shouldn’t be using it anymore anyway. So, we’ll use Genymotion for this example. Since it’s a virtual machine, and not native, you need to relay the network connection to your local machine. If you don’t, Charles can’t see the internal Genymotion network and none of the requests will be seen.

First, we’ll need the Charles certificate:

  1. Download the Charles certificate for Android.
  2. Unzip and drag and drop it on to your running Genymotion device.
  3. Run the “File Manager” app on your Genymotion device and open your Download directory there.
  4. Tap the certificate to install and name it however you wish. (You may be asked to set up a security code first.)

With that done, now you need to configure a proxy to get the network traffic running through your desktop. Follow these steps exactly:

  1. Open Settings and tap Wi-Fi to see your wireless connections.
  2. You should see something like “WiredSSID”. Tap and hold it until a menu appears.
  3. Select “Modify network”.
  4. Change the “Proxy settings” to Manual.
  5. Change the “Proxy hostname” field to 10.0.3.2. This is a special Genymotion IP address so don’t try setting it in the original Android emulator. It won’t work!
  6. Change the “Proxy port” field to 8888.
  7. Tap Save.

    Restart Charles and Genymotion to be assured of a working connection. And remember, you’ll need to perform these two operations for every new Android device you create because they’re all physically separate.

    If you now run an app you’ll see the network connections appear. You’ll still have to configure SSL settings to see encrypted traffic, but that’s explained clearly in Charles and is up to you.

    Go forth!

    • Print
    • Facebook
    • Twitter
    permalink

    , , , , , , ,

Speeding up Android Studio

I’ve been using Android Studio for about a year now, adopting it early to get a jump on things. And in that time, I’ve certainly had my share of frustrations with it, but it’s going to be a superior tool to ADT when it comes out of beta, so it’s worth it to use it now.

Here’s a few tips I’d like to share with you if you do plan on using it.

The Command/Control-E Menu

I use many IDE’s, from XCode to Sublime Text to Android Studio, and I use each one differently when it comes to file navigation. In XCode, for example, I tend to toggle the “Recent Files” option a lot so that the Project Navigator is only displaying the files I’m mucking with at that moment. In Android Studio, though, I use the Command-E menu.

Open up a bunch of files in Android Studio and then press Command-E (Control-E on inferior OS’s). You’ll see something like this:

You’ll see two columns. On the right are your most recently opened files, in descending order. You instantly have keyboard access so you can arrow up or down and press Enter to open the file you want. This is extremely handy and the optimal way to move between files in Android Studio I think.

As a bonus, on the left you’ll see a list of views you can open. This is also very useful for navigating around the IDE itself, but I encourage learning the individual keyboard shortcuts for them as that’ll be faster. Start with Command-6!

And before we continue on, you might be asking why not just navigate using the tabs? You can do that, yes. However, I find it slower, especially with a mouse. Even with the keyboard, it’s a three-key combo by default. The Command-E popup really is the fastest way to navigate…give it a try!

Avoid the Wizards

Android Studio improves on ADT when it comes to wizards that create activities, resources files, etc. for you. They are more detailed and provide more options. There’s only one problem: you shouldn’t use them.

Well, that’s not totally true. Sometimes I use a couple of them early on in a project, but I still suggest to avoid them after that. Why? Well, several reasons:

  • You should be creating a lot more fragments.
  • You shouldn’t be creating many activities that actually do anything.
  • You can’t avoid creating menu XML for each activity.
  • It’s faster not to.

Do you put all your logic in activities? You really shouldn’t. For the most part, you should be creating “shell” activities that do nothing but host a fragment for your phone-form views (or multiple fragments for your table-form views). This leaves near-empty activities in your code and, rightfully, puts that code in fragment-land!

So, why not use the wizard? Well, the wizard wants to create activities for you, not fragments, and that’s wrong. So what I’ll do when creating a new screen is to clone a previous screen from the bottom-up:

  1. Copy and paste the fragment XML from a similar screen. (Enter new filename.)
  2. Change the XML to match my screen requirements.
  3. Copy and paste the fragment class from that screen. (Enter new filename.)
  4. Change the code and handlers for my screen requirements.
  5. Copy and paste the activity class from that screen. (Enter new filename.)
  6. Direct that class to load your new fragment.
  7. Add the activity to the AndroidManifest.xml. (Or crash!)

A bunch of steps, but it’s many times faster than using an activity creation wizard. Plus, you’ll have to create your fragment yourself and re-jigger everything anyway. If you’re like me, you have a base activity that simply loads a fragment and your fragment logic always looks similar. Same for resource files.

Copy and paste is your friend…well…this time!

Genymotion

There’s no argument. You must be using Genymotion for Android development. Why? Oh, well for one thing you won’t have to wait around for an hour for your simulator to load, and you DO need simulators to test on various devices so don’t tell me to just use a device…plus, it’s nice not having a phone wired to your computer all the time, and Genymotion runs so fast and smooth and, yes, yes, I know there’s that Intel thing to make the real simulator faster and that works (after an hour of trying!) but you still have all the other problems like when you run the app and the simulator comes up and it never runs your app because it’s freakin’ disconnected and it sometimes takes forever for the app to run anyway and WHY THE HELL CAN’T GOOGLE GET THEIR SIMULATOR RIGHT ANYWAY!!!?!?!!

OK, let me breathe.

Alright, so yeah, use Genymotion. It’s not only a way better environment than Google could ever provide, but it’s super simple to install, too! (Which you wouldn’t expect from a “virtual” environment.) All you need to do is install Virtual Box (hell, you don’t even need to run in, just install it) and then install Genymotion. You’ll be shown how to create your virtual device and…oh man, it’s just so easy. Android Studio sees Genymotion as just another device like the simulator or your Nexus. It’s awesome.

And to make launching Genymotion easier right from Android Studio, there’s even a plugin.

This isn’t even a recommendation. Go get it. Stop arguing.

Download Everything

This one doesn’t really just apply to Android Studio, but I’m including it here anyway.

Download everything Google offers you. Every SDK. Every bit of sample code. Everything. Do you only use SDK 10 and the latest stuff? Whatever…just download everything!

There’s a lot of reasons for this. For one, you’ll open existing projects that will fail because they use a different SDK. You will inevitably be testing against different devices in the future. Have you considered Kindle? So, when you install Android Studio and need to download what you need to use it, do yourself a favor and just get it all now. It won’t take that long and it’ll be way better than stopping multiple times later.

DOWNLOAD ALL THE THINGS!!!

  • Print
  • Facebook
  • Twitter
permalink

, , ,

Previous Posts