Whoops, I forgot to write this post after I published Catena. So let's see if I can still remember what I learned...
1.) Spend time on what matters
If you have played Catena, then you know that I included a "Custom" mode that allows you to change almost every aspect of the game. This is actually one of my personal favorite parts of the app, because it's not just the same thing over and over. But the problem is that very few people play Custom games, because they give no sense of accomplishment. What I mean by that is that I did not provide leaderboards or achievements for Custom games. There's really no point; if you can change all of the settings for the game, then a high score is meaningless. And the only type of achievement that would make sense is one that would require the user to create an unstable game just to rack up points. Neither of these is fun or interesting, which is why I decided to keep all achievements and leaderboards exclusive to Classic and Extreme modes. But since the game itself is so simple, the only real incentive users have is to compete for leaderboard spots.
But I am drifting away from the point of this post. The biggest problem with the whole situation is that I spent a lot of time getting Custom mode to work well. It required a lot of new code, over 100 new drawables, and a significant amount of testing. I was very pleased with the result, but in the end it is just a small addition that most users might play only a few times. If I were to guess, I would say I spent about 30-40% of the entire development process working on a feature that is only used about 5% of the time. And that sucks.
So what I'm trying to say is this: Try to put the most effort into those features which will be used the most. Not that you should skimp out on quality in certain areas just because you think that it will not be used often, but if something will take you a very long time to implement, it might not be worth including right away. You can always update later if you do find the time to get everything working. That is, assuming you don't have someone over you telling you what needs to be the priority.
2.) Get the word out
Advertise your app! This is something that I did not do very well, and it shows. Personally, I hate self-promotion because it feels like I am just spamming. I included social features in my app because I don't mind other people promoting my app for me, but I have a hard time going out to websites and letting people know about my app. I've been reading some advice from other devs about how to raise your download numbers, and I think this is the most popular answer. One person I read said that he spent an entire week just posting about his app on various Android app websites. In contrast, I spent about 2 or 3 hours maximum, posting to Facebook and a few small groups I know. This is where the social features I built in don't really help. Most of the people who have downloaded my app are either friends or those friends' friends. So you can share your score on Facebook, but most of the people who see that have already heard about it, because many of them are already friends with me. Since I didn't spend much effort at all to advertise outside of my Facebook contacts, my game has gone almost completely unnoticed.
3.) App Store Optimization
This is another area in which I did not do too well. As you may know, Google Play already has over a million apps in its store. The market is already over-saturated, so unless you have a brilliant and unique idea, it will take a bit more for your app to be noticed among thousands of others. This is called "App Store Optimization", or ASO. I'm not going to pretend I really know anything about this, (as you can probably tell by looking at my app's store pages,) so if you are interested in learning more I would recommend Google. But basically the idea is to make sure that the store page for your app is as optimal as possible for bringing in downloads. I spent only a few hours creating the description and visuals for my page, because it didn't really interest me. In retrospect, I should have spent much more time on improving the quality of my store page. What's the point of spending weeks creating an app, only to have it ignored because the store page doesn't make it seem like that much fun?
I do wish that Google would let us know how many page views each store page has. That way you could see if users are having trouble finding your app in the first place, or if they can find it but simply choose not to download it.
4.) Make sure that you actually have a good idea
This one is pretty simple. Before you even start working on an app, make sure that it is something that people will actually use. To be honest, I think this is the biggest flaw with Catena. It may be addicting for the first few hours, but most people will become bored of it after a week. I personally was kind of tired of it even before release because the initial fun had already worn off while I was testing.
Of course, all of this can be ignored if you are just creating apps for fun and/or experience. If you are having fun and learning, then in the end it doesn't really matter what you are working on. I am very glad that I can say that this statement applied to me. Even though higher download numbers would be nice, in the end the development experience is what counts.
This may be my last post, at least for long time. I have returned to school for my final year, and I won't have much time for Android development, aside from small updates to my already published apps. So... yep. Goodbye I guess.
0x5453
Wednesday, September 3, 2014
Friday, August 8, 2014
Catena
I have just released another app: Catena!
Catena is a game with a simple premise: Tap a tile to make that tile start rotating. As tiles rotate, they will hit their neighbors, which will make the neighbors rotate. As you can imagine, this process can explode in a chain reaction of turning tiles. The goal is to create the biggest possible chain from your initial tap.
In Classic mode, that's all there is to it. But in Extreme mode, various power-ups will spawn that can drastically effect the course of the game. For example, one power-up will turn large groups of tiles at once, and another will give you an extra tap to spend as you choose. And if that's not enough, I've also included Custom mode, where you can control almost every single aspect of the game. You can change how the tiles are laid out, how often to spawn power-ups, the number of sides that will set off other tiles, etc.. According to my calculations, there are nearly 10 million different combinations of games you can play in custom mode. Pretty sweet.
I've added some other cool things too: You can customize the entire look and feel of the game. Colors, tile styles, and sound effects can all be changed at your leisure. I've also included Google Play Games Services, so you can earn achievements and post your high scores to leaderboards, as well as the ability to post your scores to Facebook to brag to your friends.
And as with my previous app, Catena is free to download! So what are you waiting for?
As always, if you download, be sure to rate and review! I am always open to comments and suggestions.
Catena is a game with a simple premise: Tap a tile to make that tile start rotating. As tiles rotate, they will hit their neighbors, which will make the neighbors rotate. As you can imagine, this process can explode in a chain reaction of turning tiles. The goal is to create the biggest possible chain from your initial tap.
In Classic mode, that's all there is to it. But in Extreme mode, various power-ups will spawn that can drastically effect the course of the game. For example, one power-up will turn large groups of tiles at once, and another will give you an extra tap to spend as you choose. And if that's not enough, I've also included Custom mode, where you can control almost every single aspect of the game. You can change how the tiles are laid out, how often to spawn power-ups, the number of sides that will set off other tiles, etc.. According to my calculations, there are nearly 10 million different combinations of games you can play in custom mode. Pretty sweet.
I've added some other cool things too: You can customize the entire look and feel of the game. Colors, tile styles, and sound effects can all be changed at your leisure. I've also included Google Play Games Services, so you can earn achievements and post your high scores to leaderboards, as well as the ability to post your scores to Facebook to brag to your friends.
And as with my previous app, Catena is free to download! So what are you waiting for?
As always, if you download, be sure to rate and review! I am always open to comments and suggestions.
Wednesday, August 6, 2014
Facebook integration, App Link Hosting
So my goal today was to enable users to share their score on Facebook after each round. It took me the entire day, but I did finally get it working. I ran into quite a few problems and I couldn't find any answers online, so, as always, I hope this post can help someone out.
After importing the code with the help of the Getting Started guide, I jumped straight to the Sharing section. This part of the guide is quite good, and within a few minutes I was able to share a post to my timeline. And after looking through the documentation for a few minutes, I found that Facebook provides an App Link Hosting API, so you can link back to your app from Facebook. It's actually really cool; If you are on mobile and you already own the app, clicking the link immediately launches that app. If you don't have the app installed or you are on an unsupported platform, it launches Google Play to give you a chance to download it. Unfortunately, the documentation for this feature is much less impressive than the Getting Started guide. So it took a few hours of digging, Googling, and trial-and-error to figure out exactly what you need to do to set it up. Looking back, all of the information you need can be found in the documentation, but it's quite spread out and it's not incredibly clear which steps are required. But I digress...
Okay, so here are the steps to create a new App Link object:
This one was due to using the wrong value for "access_token". Before I read the access tokens documentation, I tried to use the "Client Token" value from the app page. Clearly that doesn't work.
After importing the code with the help of the Getting Started guide, I jumped straight to the Sharing section. This part of the guide is quite good, and within a few minutes I was able to share a post to my timeline. And after looking through the documentation for a few minutes, I found that Facebook provides an App Link Hosting API, so you can link back to your app from Facebook. It's actually really cool; If you are on mobile and you already own the app, clicking the link immediately launches that app. If you don't have the app installed or you are on an unsupported platform, it launches Google Play to give you a chance to download it. Unfortunately, the documentation for this feature is much less impressive than the Getting Started guide. So it took a few hours of digging, Googling, and trial-and-error to figure out exactly what you need to do to set it up. Looking back, all of the information you need can be found in the documentation, but it's quite spread out and it's not incredibly clear which steps are required. But I digress...
Okay, so here are the steps to create a new App Link object:
- If you haven't yet, follow the Getting Started guide and link your app to a Facebook App. Under the Settings section, make sure you fill out Package Name and Class Name, and turn on Single Sign On and Deep Linking.
- You need to find your App Token. You can do this by pasting this URL in your browser (replace {app-id} and {app-secret} with the values from the Facebook app page): https://graph.facebook.com/oauth/access_token?client_id={app-id}&client_secret={app-secret}&grant_type=client_credentials
- To create the App Link object, you can use cURL from a terminal. If you don't have cURL, you can use another method. Replace the fields in this command with the appropriate values:
{custom-uri} should follow typical URI structure. You can do really complicated structures if you want, but in my case I only needed one to link to the main activity. So for example, mine looked like: my-app-name://launch - Take the ID from the response and use it to get the final link: https://fb.me/{app-link-id} (If that link doesn't work, use the official method to get the URL)
I ran into a few errors while I was figuring all of this out. So if you get any of these while trying to create the App Link, you might have made the same mistakes I did:
This one was due to using the wrong value for "access_token". Before I read the access tokens documentation, I tried to use the "Client Token" value from the app page. Clearly that doesn't work.
I then tried to use the Graph API Explorer to generate a token. But that generates a User Access token, not an App Access token, so that didn't work either.
This one was due to copy-pasting the wrong App Access token. Somehow I must have grabbed a sample token or something, because the app ID was 123456789000000, haha. Not sure how I missed that one.
Anyway, that's about it. Hopefully this helps someone at some point!
Thursday, July 31, 2014
#threadproblems
Ran into another interesting bug yesterday. In retrospect, I am actually really proud of the fact that I was able to find the issue and fix it so quickly. Thankfully, this time the bug was not due to my own stupidity.
I'm not sure if I have mentioned it yet, but I am currently working on a game. I'll try to keep the details sparse, because they don't really matter. In this game, you have a certain number of clicks to use, and after you run out of clicks, the game ends. Sometimes, this would behave as expected. But the bug was that most of the time when you used your final click, the game would immediately end instead of handling that click.
The game runs in a separate thread from that which handles touch events, so after briefly reviewing my logic it seemed pretty clear that it was some threading problem. I will admit that multi-threading is probably my weakest area of expertise, mainly due to a simple lack of experience. (That, and the fact that I have only had one professor briefly teach threading, and he is one of the worst teachers in our department, so I did not pay much attention.) So maybe this was actually my fault. Maybe I broke some sort of fundamental law of threading, I'm not sure. But anyway, in my game thread, I have a while loop that keeps looping as long as the player has clicks left, and another while loop inside it that runs the actions caused by the player's click. Both of those loop variables were being changed from the UI thread instead of the game thread. So, it basically looked like this:
And the thread that was modifying those values looked something like this:
Now this wasn't too hard; if the touch event happened while the game thread was at the top of the outer loop, (between lines 1 and 2,) it would work fine. Otherwise, it would ignore the last click because _clicksLeft == 0, so it would never even get into the outer loop. So I quickly added another condition to the loop:
Of course, it probably wouldn't happen with exactly this timing, but it's possible. The solution is simple though: just reverse the loop conditions:
Regardless of the execution timing, there is now no case where both conditions are false unless the game is really over. Pretty crazy, I never would have thought the order of loop conditions could be such a problem! Whoever first said "You learn something new every day," I think they might have been on to something.
I'm not sure if I have mentioned it yet, but I am currently working on a game. I'll try to keep the details sparse, because they don't really matter. In this game, you have a certain number of clicks to use, and after you run out of clicks, the game ends. Sometimes, this would behave as expected. But the bug was that most of the time when you used your final click, the game would immediately end instead of handling that click.
The game runs in a separate thread from that which handles touch events, so after briefly reviewing my logic it seemed pretty clear that it was some threading problem. I will admit that multi-threading is probably my weakest area of expertise, mainly due to a simple lack of experience. (That, and the fact that I have only had one professor briefly teach threading, and he is one of the worst teachers in our department, so I did not pay much attention.) So maybe this was actually my fault. Maybe I broke some sort of fundamental law of threading, I'm not sure. But anyway, in my game thread, I have a while loop that keeps looping as long as the player has clicks left, and another while loop inside it that runs the actions caused by the player's click. Both of those loop variables were being changed from the UI thread instead of the game thread. So, it basically looked like this:
And the thread that was modifying those values looked something like this:
Now this wasn't too hard; if the touch event happened while the game thread was at the top of the outer loop, (between lines 1 and 2,) it would work fine. Otherwise, it would ignore the last click because _clicksLeft == 0, so it would never even get into the outer loop. So I quickly added another condition to the loop:
Fixed! ...Or so I thought. I tested it a few times, and it looked like the bug was gone, so I pushed my code and went to go work on something else. Then, I was testing a couple days later, and I ran into the bug again. D:
Clearly my fix had done something, because I didn't see the bug again for a few days. So I tested it a few more times to make sure my eyes weren't playing tricks on me, and it happened again. *sigh* Time to go back to the code... The first thing that came to mind was to try to syncronize the blocks that were dealing with those variables. Unfortunately, that froze my entire UI, so that didn't seem to be an option. I stared at it for about an hour, and then it hit me:
There is a rare edge case where both of the above loop conditions will be false, even if the game is not supposed to be over. _isRunningTurn must be false, _clicksLeft must be 1, and the touch event must happen while the game thread is in the middle of checking the loop condition! If I spread out the timing, it becomes a bit more obvious:
Of course, it probably wouldn't happen with exactly this timing, but it's possible. The solution is simple though: just reverse the loop conditions:
Regardless of the execution timing, there is now no case where both conditions are false unless the game is really over. Pretty crazy, I never would have thought the order of loop conditions could be such a problem! Whoever first said "You learn something new every day," I think they might have been on to something.
Monday, July 21, 2014
Google Play Game Services
A few days ago I finished adding Google Play Game Services to my app, and I wanted to make a quick post about an issue I ran into.
The problem that I had was not mentioned anywhere in the Getting Started guide, and apparently it's not a very common one. It took me a few hours before I finally found the answer online, in a StackOverflow answer that was sitting at the bottom of the page with 0 points.
Specifically, the problem was that whenever I would try to log in to the game services, I would get an error that simply said: "Unknown issue with Google Play Services." The log output wasn't much more helpful, it just said "Internal error, see log for details." So I had to find the actual error in the unfiltered logs (which is a challenge in itself), which said: "Access Not Configured. Please use Google Developers Console to activate the API for your project." This didn't really help, because Google had already activated the API's that I was supposed to need.
So I went back to make sure I hadn't missed anything. I followed the Getting Started guide to the letter. This is important, because there are actually a lot of things that can go wrong, and they are all pretty easy to miss. I double and triple checked all of the common problems. Nope.
Eventually I went back to look at the error message again. The error also included a link to a troubleshooting page, but I had already looked over all of the solutions it offered. However, this time I realized that the link was not to a Google Play troubleshooting page, but a Google Drive troubleshooting page. So I just decided to activate the Drive API on the off chance that it would work. And wouldn't you know, that was the problem! I eventually found this solution in a neglected StackOverflow answer, although I'm still not 100% sure why it was necessary. It automatically activated the other API's, even though I don't need half of them, so why doesn't it just activate the Drive API as well?
The problem that I had was not mentioned anywhere in the Getting Started guide, and apparently it's not a very common one. It took me a few hours before I finally found the answer online, in a StackOverflow answer that was sitting at the bottom of the page with 0 points.
Specifically, the problem was that whenever I would try to log in to the game services, I would get an error that simply said: "Unknown issue with Google Play Services." The log output wasn't much more helpful, it just said "Internal error, see log for details." So I had to find the actual error in the unfiltered logs (which is a challenge in itself), which said: "Access Not Configured. Please use Google Developers Console to activate the API for your project." This didn't really help, because Google had already activated the API's that I was supposed to need.
So I went back to make sure I hadn't missed anything. I followed the Getting Started guide to the letter. This is important, because there are actually a lot of things that can go wrong, and they are all pretty easy to miss. I double and triple checked all of the common problems. Nope.
Eventually I went back to look at the error message again. The error also included a link to a troubleshooting page, but I had already looked over all of the solutions it offered. However, this time I realized that the link was not to a Google Play troubleshooting page, but a Google Drive troubleshooting page. So I just decided to activate the Drive API on the off chance that it would work. And wouldn't you know, that was the problem! I eventually found this solution in a neglected StackOverflow answer, although I'm still not 100% sure why it was necessary. It automatically activated the other API's, even though I don't need half of them, so why doesn't it just activate the Drive API as well?
Friday, July 11, 2014
Creating a Reusable Dialog Class
The past couple of days I have been working on a task that I thought would be a much more common problem. However, after much Google searching I couldn't find a single page that could help. So, as always, I'm going to dump my code here in case it can help out someone in the future.
I was trying to make my own Dialog class that I could reuse throughout my app. Since I had to use it in many different ways, I needed to be able to put a custom layout in the middle of the dialog, similar to how the AlertDialog class works. This was the biggest issue; I wanted to completely change the way my dialogs looked, more than you can change by simply creating a style. (Although after looking through the code, I realize that a style could have changed a lot more than I originally thought.) Finally, I wanted to be able to change the colors used in the dialog on the fly. This last one is not really related to the rest, but is just something I am trying to do everywhere in my app. (I'm mentioning it now because my code would be pretty confusing otherwise.) You can see my original concept over to the right, to get an idea of what I was aiming for.
So, here is all of my code:
two_color_dialog.xml
TwoColorDialog.java
dialog_background.xml
dialog_title_box.xml
dialog_close_button.xml
I'm not going to talk about the color-changing aspect, because that's not really the point of the post. There are a couple things I would like to talk about though.
Notice in the dialog's layout, I have a LinearLayout (id/dialog_background) within a FrameLayout, which I use to show the background drawable. Why not just put the background on the FrameLayout and get rid of that LinearLayout? Well, the background drawable automatically surrounds all of its children. Since I wanted the title and close button to float a bit outside of the main dialog, I had to put those two elements outside of the layout that contains the rest of the "inner" dialog elements, so that they wouldn't be surrounded by the background.
The big thing here is the FrameLayout that holds the custom view. This is actually the same method that is used for the AlertDialog class; I ended up looking through the source code for some inspiration. And although I have a lot of code up there, it all really boils down to this:
This is how you can stick different views inside of the dialog, allowing you to use it in different places. It seems pretty simple, but I bring it up because it took me much longer to figure out than I would care to admit. In fact, that is the first of the big issues I ran in to:
Problems and bugs
1.) It took me way too long to figure out how to embed a custom layout within the dialog's layout.
I tried a bunch of different ideas. It seemed like a pretty simple task, but no matter what I tried I couldn't get it to work. I couldn't find any examples online, except for one StackOverflow question. I tried all of the different answers that were suggested, but no dice. I even eventually went to the AlertDialog source code, but even after using the same method, I just couldn't get the inner view to show up!
Of course, the whole time I assumed that my code to display the view was at fault. I took for granted some things that I assumed could never be the problem. (I tend to do that a lot.) In the end, the problem was NOT with my code, but with the layout I was trying to display. When I was trying to test the dialog, I just grabbed a random layout that I was already using elsewhere in my app. The layout that I chose contained only a single ListView. Of course, if you don't set up an adapter for a ListView, it just shows up empty. So my code was working the whole time, but I thought it wasn't because I was adding an empty layout.
2.) The backgrounds of my button drawables were turning black for no reason.
The link above explains the issue pretty well: My buttons were working fine, but for no reason whatsoever the backgrounds suddenly all turned black. I still have absolutely no idea why this happened. I was able to fix the problem by explicitly adding a transparent background to the drawable.
3.) The shadow of my "close" button would disappear after the dialog was shown the first time.
This is even stranger than the last one: My dialog would look perfect, but only the first time it was displayed. After that, the shadow of the close button would disappear. (See the link above for a picture.) I tried debugging to see what was different between the two states. I tried Googling, but my search turned up empty. I tried asking StackOverflow, but all I got was a single comment that didn't even address the issue. As a last resort, I just tried changing things in the drawable to try to find the problem. I knew that the title box didn't have any issues, and that was almost identical. After much trial and error, I narrowed down the problem to the "padding" tag. For whatever reason, the problem only happens when I am using that tag. So I took it out and added position attributes to all of the other layers instead. Again, I have no idea what caused this; I can only assume that it is actually an Android bug.
Finally, here is an example of how to use the dialog:
And here is what the final result looks like:
Not too bad! Obviously not perfect; it only has the functionality that I needed at the moment, so it's not nearly as robust as the original AlertDialog class. But it's definitely a start!
So, here is all of my code:
two_color_dialog.xml
TwoColorDialog.java
dialog_background.xml
dialog_title_box.xml
dialog_close_button.xml
I'm not going to talk about the color-changing aspect, because that's not really the point of the post. There are a couple things I would like to talk about though.
Notice in the dialog's layout, I have a LinearLayout (id/dialog_background) within a FrameLayout, which I use to show the background drawable. Why not just put the background on the FrameLayout and get rid of that LinearLayout? Well, the background drawable automatically surrounds all of its children. Since I wanted the title and close button to float a bit outside of the main dialog, I had to put those two elements outside of the layout that contains the rest of the "inner" dialog elements, so that they wouldn't be surrounded by the background.
The big thing here is the FrameLayout that holds the custom view. This is actually the same method that is used for the AlertDialog class; I ended up looking through the source code for some inspiration. And although I have a lot of code up there, it all really boils down to this:
This is how you can stick different views inside of the dialog, allowing you to use it in different places. It seems pretty simple, but I bring it up because it took me much longer to figure out than I would care to admit. In fact, that is the first of the big issues I ran in to:
Problems and bugs
1.) It took me way too long to figure out how to embed a custom layout within the dialog's layout.
I tried a bunch of different ideas. It seemed like a pretty simple task, but no matter what I tried I couldn't get it to work. I couldn't find any examples online, except for one StackOverflow question. I tried all of the different answers that were suggested, but no dice. I even eventually went to the AlertDialog source code, but even after using the same method, I just couldn't get the inner view to show up!
Of course, the whole time I assumed that my code to display the view was at fault. I took for granted some things that I assumed could never be the problem. (I tend to do that a lot.) In the end, the problem was NOT with my code, but with the layout I was trying to display. When I was trying to test the dialog, I just grabbed a random layout that I was already using elsewhere in my app. The layout that I chose contained only a single ListView. Of course, if you don't set up an adapter for a ListView, it just shows up empty. So my code was working the whole time, but I thought it wasn't because I was adding an empty layout.
2.) The backgrounds of my button drawables were turning black for no reason.
The link above explains the issue pretty well: My buttons were working fine, but for no reason whatsoever the backgrounds suddenly all turned black. I still have absolutely no idea why this happened. I was able to fix the problem by explicitly adding a transparent background to the drawable.
3.) The shadow of my "close" button would disappear after the dialog was shown the first time.
This is even stranger than the last one: My dialog would look perfect, but only the first time it was displayed. After that, the shadow of the close button would disappear. (See the link above for a picture.) I tried debugging to see what was different between the two states. I tried Googling, but my search turned up empty. I tried asking StackOverflow, but all I got was a single comment that didn't even address the issue. As a last resort, I just tried changing things in the drawable to try to find the problem. I knew that the title box didn't have any issues, and that was almost identical. After much trial and error, I narrowed down the problem to the "padding" tag. For whatever reason, the problem only happens when I am using that tag. So I took it out and added position attributes to all of the other layers instead. Again, I have no idea what caused this; I can only assume that it is actually an Android bug.
Finally, here is an example of how to use the dialog:
And here is what the final result looks like:
Not too bad! Obviously not perfect; it only has the functionality that I needed at the moment, so it's not nearly as robust as the original AlertDialog class. But it's definitely a start!
Wednesday, July 2, 2014
Colors Are Hard, Man
I ran into a pretty funny bug last night. In this app, I am allowing users to change the background color. The choice of background color is stored in a SharedPreference. As you may know, every time you read from SharedPreferences, you have to provide a default value for the preference you are reading. I was reading an int, so I used a default value of -1 to check if the preference was initialized or not. My code looked like this:
However, the above exception was being thrown every single time, and I couldn't figure out why. I double- and triple-checked the names for typos, just in case I was assigning to "backgronud" or something like that. Nope, no typos. So here's where I initialize the preference, in my main activity's onCreate:
I threw that log message in there to confirm that, yes, the preference was definitely getting initialized. But then why was it going with the default -1 and throwing that exception? I had to break out the debugger before I figured it out.
So, in Android, colors are stored as integers, (which are 32 bits,) with 8 bits for each channel: Alpha, Red, Green, and Blue. So if you write out the value for a color in hex, it takes this format: 0xAARRGGBB. So, for example, the color Green has a value of 0xFF00FF00, and White would be 0xFFFFFFFF. Do you see the issue here?
If not, read up a bit on Two's Compliment and then come back here.
Done? Good.
0xFFFFFFFF in 32-bit Two's Compliment is -1. It was initializing to the same value that I was using to see if it was not initialized. So it would start out at -1, and then throw the error because I thought that a color would never have a value of -1. How wrong I was.
Of course, this is easily fixed by choosing a color value that actually will never be used. I went with 0 instead of -1. I could also just delete the error checking and make it default to white everywhere, but I prefer having the default value assigned in only one place, making it easier to change later.
However, the above exception was being thrown every single time, and I couldn't figure out why. I double- and triple-checked the names for typos, just in case I was assigning to "backgronud" or something like that. Nope, no typos. So here's where I initialize the preference, in my main activity's onCreate:
I threw that log message in there to confirm that, yes, the preference was definitely getting initialized. But then why was it going with the default -1 and throwing that exception? I had to break out the debugger before I figured it out.
So, in Android, colors are stored as integers, (which are 32 bits,) with 8 bits for each channel: Alpha, Red, Green, and Blue. So if you write out the value for a color in hex, it takes this format: 0xAARRGGBB. So, for example, the color Green has a value of 0xFF00FF00, and White would be 0xFFFFFFFF. Do you see the issue here?
If not, read up a bit on Two's Compliment and then come back here.
Done? Good.
0xFFFFFFFF in 32-bit Two's Compliment is -1. It was initializing to the same value that I was using to see if it was not initialized. So it would start out at -1, and then throw the error because I thought that a color would never have a value of -1. How wrong I was.
Of course, this is easily fixed by choosing a color value that actually will never be used. I went with 0 instead of -1. I could also just delete the error checking and make it default to white everywhere, but I prefer having the default value assigned in only one place, making it easier to change later.
Subscribe to:
Posts (Atom)