<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Mithat Sinan Sarı on Medium]]></title>
        <description><![CDATA[Stories by Mithat Sinan Sarı on Medium]]></description>
        <link>https://medium.com/@msinansari?source=rss-dff82d2ac66e------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/2*UnshIH51GPgHxGleLdlzKA.png</url>
            <title>Stories by Mithat Sinan Sarı on Medium</title>
            <link>https://medium.com/@msinansari?source=rss-dff82d2ac66e------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 22:55:01 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@msinansari/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[The Evolution of Blog Writing: Unveiling the Impact of AI]]></title>
            <link>https://msinansari.medium.com/the-evolution-of-blog-writing-unveiling-the-impact-of-ai-d7364e052045?source=rss-dff82d2ac66e------2</link>
            <guid isPermaLink="false">https://medium.com/p/d7364e052045</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[chatgpt]]></category>
            <dc:creator><![CDATA[Mithat Sinan Sarı]]></dc:creator>
            <pubDate>Wed, 23 Aug 2023 20:04:05 GMT</pubDate>
            <atom:updated>2023-08-23T20:04:05.639Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/512/1*Rd1jhjejs3QlgIkdpTSaZg.png" /></figure><p>In an age dominated by technological advancement, the world of writing has not remained untouched. From news articles to creative pieces, AI has managed to infiltrate every corner of the writing realm, and blog posts are no exception. But what exactly is the effect of AI on blog posts? Are we witnessing a new era of efficiency and creativity, or are we slowly drifting away from the authenticity that once defined our writing?</p><p><strong>The Pros: Enhancing Efficiency and Creativity</strong></p><ol><li><strong>Increased Efficiency</strong>: One of the most obvious benefits of incorporating AI into blog writing is the efficiency it offers. AI-powered tools can generate content at a lightning-fast pace, significantly reducing the time required to produce a blog post. Writers can now focus on refining ideas and adding a personal touch rather than spending hours drafting the initial content.</li><li><strong>Consistency and Accuracy</strong>: AI is capable of maintaining a consistent tone and style throughout a blog post, ensuring a seamless reading experience. Moreover, it minimizes the chances of grammatical errors and typos, contributing to the overall professionalism of the piece.</li><li><strong>Content Ideas and Research:</strong> AI tools can aid in generating fresh content ideas and conducting extensive research on a given topic. This can be a boon for writers struggling with writer’s block or seeking to explore new niches.</li><li><strong>Multilingual Blogging:</strong> Language barriers can be transcended with the help of AI translation tools, allowing bloggers to reach a wider global audience without being limited by linguistic constraints.</li></ol><p><strong>The Cons: Straying from Authenticity and Creativity</strong></p><ol><li><strong>Lack of Human Touch:</strong> While AI can mimic writing styles, it often lacks the emotional depth and nuanced perspectives that human writers bring to their work. The emotional connection between the writer and the reader can sometimes be lost when AI takes the lead.</li><li><strong>Template-Driven Content:</strong> Some AI-generated content may appear formulaic or template-driven, lacking the uniqueness that readers seek in a well-crafted blog post. This could lead to a decline in reader engagement and loyalty.</li><li><strong>Ethical Concerns:</strong> As AI evolves, it raises ethical questions about plagiarism and originality. AI tools sometimes inadvertently generate content that closely resembles existing work, potentially leading to copyright disputes.</li><li><strong>Moving Away from Reality:</strong> The convenience of AI-generated content might tempt writers to rely solely on automation, gradually moving away from authentic experiences and firsthand observations. This could erode the genuine, relatable essence of blog writing.</li></ol><p><strong>Striking a Balance: Human Creativity and AI Assistance</strong></p><p>Rather than viewing AI as an adversary, bloggers can harness its power as a tool for enhancement. Here’s how:</p><ol><li><strong>Content Ideation:</strong> Allow AI to provide you with content ideas and data-backed insights, but infuse your unique perspectives and experiences into the narrative.</li><li><strong>Draft Generation:</strong> Utilize AI to draft an initial outline or framework, then dive in to enrich it with your distinctive voice, anecdotes, and personal touch.</li><li><strong>Editing and Refinement:</strong> Rely on AI-powered editing tools for grammar and style checks, but don’t overlook the value of human editing to preserve authenticity.</li><li><strong>Innovation:</strong> Experiment with incorporating AI-generated elements, such as infographics or data visualizations, into your blog to enhance reader engagement.</li></ol><p>Before we wrap up this journey into the realm of AI-powered blog writing, let’s unveil a little secret. Yes, you guessed it — this very blog post you’ve been reading was penned by none other than an AI. But fear not, dear reader, no humans were harmed in the making of this content!</p><p>In a twist that even the best mystery novels would envy, the very words you’ve just absorbed were crafted by lines of code and neural networks. It seems the AI uprising is not quite a Terminator-style apocalypse, but rather a friendly assistant eager to help create insightful, engaging content.</p><p>So, as you contemplate the fascinating relationship between AI and the art of blogging, take a moment to appreciate the irony that an AI has dissected the topic of its own influence. Remember, whether it’s AI or human creativity, the essence of a captivating blog post remains the same — a connection between the writer’s mind and the reader’s imagination.</p><p>Until next time, fellow enthusiasts of the written word and unwitting participants in the AI takeover — keep writing, keep reading, and keep those virtual cogs and gears turning!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d7364e052045" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Improving Your Algorithm Knowledge with LeetCode: The Journey]]></title>
            <link>https://msinansari.medium.com/improving-your-algorithm-knowledge-with-leetcode-the-journey-497029e4cf67?source=rss-dff82d2ac66e------2</link>
            <guid isPermaLink="false">https://medium.com/p/497029e4cf67</guid>
            <category><![CDATA[algorithms]]></category>
            <category><![CDATA[data-structures]]></category>
            <category><![CDATA[leetcode]]></category>
            <category><![CDATA[github]]></category>
            <category><![CDATA[kotlin]]></category>
            <dc:creator><![CDATA[Mithat Sinan Sarı]]></dc:creator>
            <pubDate>Wed, 15 Feb 2023 19:36:27 GMT</pubDate>
            <atom:updated>2023-02-15T19:36:27.364Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Oie4j4fp6F0V7EC9T75NPA.jpeg" /></figure><p>As a developer, it’s important to stay sharp and continuously improve your skills. One way to do this is to practice solving algorithm and data structure problems, such as those found on <a href="https://leetcode.com/">LeetCode</a>. Not only does this help you to improve your problem-solving skills, but it also has many other benefits. However, merely solving the problems may not be enough to truly master the concepts. That’s where keeping a coding journal comes in.</p><p><strong>A coding journal</strong> is a personal collection of notes and solutions to coding problems, often stored in a notebook or a digital file. By keeping a coding journal, you can record your thought process as you work through a problem, document your solutions, and reflect on what you’ve learned. This process can help you to better understand the concepts and to reinforce your knowledge over time.</p><ol><li><strong>Write down your thought process:</strong> As you work through a coding problem, jot down your ideas, assumptions, and hypotheses. This will help you to organize your thoughts and to identify areas where you might be struggling.</li><li><strong>Document your solutions:</strong> Once you’ve solved a problem, write down your code and any notes or insights that you gained. This will help you to remember your solution and to compare it to other solutions in the future.</li><li><strong>Reflect on what you’ve learned</strong>: After you’ve solved a problem, take some time to reflect on what you’ve learned. This could include summarizing the key concepts, identifying common patterns or tricks, or thinking about how you could apply what you’ve learned to other problems. And spend some time to check out how other developers have solved the problem to gain new perspectives.</li><li><strong>Review your journal regularly</strong>: Make a habit of reviewing your journal regularly to reinforce your understanding of the concepts and to identify areas where you might need more practice.</li></ol><p>Here is the GitHub repository of my coding journal. In the repo, you can find some solutions and notes that I took.</p><p><a href="https://github.com/mitsinsar/TheJourney">GitHub - mitsinsar/TheJourney</a></p><p>And here are some of the gains you can experience from solving algorithm problems:</p><ol><li><strong>Improved problem-solving skills</strong>: Solving algorithm problems is an excellent way to improve your problem-solving skills. It teaches you to break down complex problems into smaller, more manageable pieces and to think critically about how to solve them.</li><li><strong>Increased familiarity with common algorithms and data structures</strong>: By solving a variety of algorithm problems, you’ll become more familiar with common algorithms and data structures, which can help you to recognize patterns and apply them to other problems.</li><li><strong>Sharpened programming skills</strong>: Solving algorithm problems requires you to write efficient and optimized code. By practicing this skill, you’ll become a better programmer overall and be able to write cleaner, more efficient code in other areas.</li><li><strong>Improved performance in technical interviews</strong>: Many technical interviews for software engineering positions involve algorithm problems. By solving these problems regularly, you’ll be better prepared for technical interviews and more confident in your abilities.</li></ol><p>By keeping a coding journal, you can improve your algorithm knowledge in a more structured and deliberate way. You’ll be able to see your progress over time, document your solutions and thought process, and reflect on what you’ve learned. And by regularly solving algorithm problems, you’ll improve your problem-solving skills, programming skills, and performance in technical interviews. So, start practicing today and see how your skills can improve over time!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=497029e4cf67" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Handle Process Death on Android]]></title>
            <link>https://medium.com/hipo-labs/how-to-handle-android-process-death-9305822282a0?source=rss-dff82d2ac66e------2</link>
            <guid isPermaLink="false">https://medium.com/p/9305822282a0</guid>
            <category><![CDATA[androiddev]]></category>
            <category><![CDATA[android-apps]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[savedstatehandle]]></category>
            <dc:creator><![CDATA[Mithat Sinan Sarı]]></dc:creator>
            <pubDate>Sat, 22 Jan 2022 04:52:46 GMT</pubDate>
            <atom:updated>2022-01-27T10:59:41.871Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QhoziXYV1pBKBT0-UsHwwg.jpeg" /><figcaption><a href="https://unsplash.com/photos/ZLHBjxbCCEc">Stone Church</a> by <a href="https://unsplash.com/@scottrodgerson">Scott Rodgerson</a></figcaption></figure><p>Have you ever thought about what happens after we die? No one knows. But there is something we know: When an app dies, we lose state.</p><p>Unfortunately mobile devices don’t have unlimited battery or unlimited memory. That’s why when system needs more memory, it kills processes to make some room. So we should make sure that our apps are not effected by this process death. Although it is very important in terms of user experience, the solution is quite easy.</p><p>Let’s see an example; We have an application where we fetch user details when Get User button is clicked. This is the initial state of the application.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/381/1*JoG1sVxVl6BuLqRqmas93w.jpeg" /></figure><p>When we click on Get User button, we fetch user details from an API and update the UI.</p><p>We have a simple ViewModel that keeps the UI state;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e433412a7e14122c6811fc1e79fbf1fe/href">https://medium.com/media/e433412a7e14122c6811fc1e79fbf1fe/href</a></iframe><p>And this is our UI state class, just a simple data class.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/37ae7ec02165767c2cda8783ea6e6cd8/href">https://medium.com/media/37ae7ec02165767c2cda8783ea6e6cd8/href</a></iframe><p>Since we keep the UI state reference in ViewModel, application will not lose state when we rotate the device or send the application to the background.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/549/1*L4yz6F76QqtZSPuN4Re7pw.jpeg" /></figure><p>BUT if system kills the app when the application is on the background, user will see the initial/empty state when the app is reopened.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/229/1*ivACaEw8ptUGIWyubl1tKQ.png" /></figure><p>To simulate process death in Android Studio; <br>Go to <em>Logcat</em> -&gt; <em>Terminate Application</em></p><p>Or you can enable “Don’t keep activities” option in Developer Settings of the device.</p><p>So how do we prevent state loss in this situation? How do we save the state and how do we handle it? We use SavedStateHandle:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/33fa7428ebb294f395f1b72874505379/href">https://medium.com/media/33fa7428ebb294f395f1b72874505379/href</a></iframe><p>As soon as we update the UI state, we save it to the SavedStateHandle and when app is relaunched, we check if we have saved state or not. It is that simple!</p><p>There are some key points that we need to consider while applying this solution;</p><ul><li>Data we save in SavedStateHandle must be Parcelable so that we can write it to the bundle.</li><li>The amount of data should be kept small because the system process needs to hold on to the provided data for as long as the user can ever navigate back to that activity. Android recommends that we should keep saved state to less than 50k of data.</li></ul><p>You can check the sample application in the repo below:</p><ul><li><a href="https://github.com/mitsinsar/SavedStateHandleExample">GitHub - mitsinsar/SavedStateHandleExample</a></li><li><a href="https://medium.com/androiddevelopers/who-lives-and-who-dies-process-priorities-on-android-cb151f39044f">Who lives and who dies? Process priorities on Android</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9305822282a0" width="1" height="1" alt=""><hr><p><a href="https://medium.com/hipo-labs/how-to-handle-android-process-death-9305822282a0">How to Handle Process Death on Android</a> was originally published in <a href="https://medium.com/hipo-labs">Hipo</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Customizing MaterialTimePicker]]></title>
            <link>https://msinansari.medium.com/customizing-materialtimepicker-61759abd034f?source=rss-dff82d2ac66e------2</link>
            <guid isPermaLink="false">https://medium.com/p/61759abd034f</guid>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[material-design]]></category>
            <category><![CDATA[material-time-picker]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[androiddev]]></category>
            <dc:creator><![CDATA[Mithat Sinan Sarı]]></dc:creator>
            <pubDate>Thu, 11 Feb 2021 22:23:04 GMT</pubDate>
            <atom:updated>2021-02-12T06:47:32.901Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/910/1*66SfGGATI6WDXeJi8pAvTQ.jpeg" /></figure><p>I’ve been trying to customize <strong>MaterialTimePicker</strong> and couldn’t find proper documentation about it. So I decided to make one. I will try to explain as simple as possible.</p><p>If you find this blog post, I believe you already know about MaterialTimePicker. That’s why I am not going to explain what it is.</p><p>Still want to learn about more; <a href="https://developer.android.com/reference/com/google/android/material/timepicker/package-summary?hl=es-419">overview documentation.</a></p><p>This is what we are going to build. You can find demo app <a href="https://github.com/mitsinsar/MaterialTimePickerDemo">here</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/654/1*ur5lwppVqjtdTMQ6RxxcnQ.png" /></figure><h4>Customizing Clock Face</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/638/1*m5AmHDb6SOoX6QL0qf1eZA.png" /></figure><p>Name of the attributes are pretty self explanatory;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5e05735e0a279ee4c73d1b596400760d/href">https://medium.com/media/5e05735e0a279ee4c73d1b596400760d/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a784bc8b80ae047d232969dff7479116/href">https://medium.com/media/a784bc8b80ae047d232969dff7479116/href</a></iframe><h4>Customizing Keyboard Icon</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/130/1*39peMOmWKWFII32t5SR9YQ.png" /></figure><p>This one is a button. You can change tint, ripple, shape and stuff.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/eb88125ebe2a2799b2c79a958906f287/href">https://medium.com/media/eb88125ebe2a2799b2c79a958906f287/href</a></iframe><h4>Customizing AM/PM Selection</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/144/1*ZvTRiXVfQVLSMjdt_9knpQ.png" /></figure><p>This one is also a button;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5937dd65869212bfe7509cfe87ee3837/href">https://medium.com/media/5937dd65869212bfe7509cfe87ee3837/href</a></iframe><p>Background is a simple selector;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cedaf0408db6a50390630bfb9732f536/href">https://medium.com/media/cedaf0408db6a50390630bfb9732f536/href</a></iframe><h4>Customizing Title</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/284/1*aBwXA1dQP-uX3SyhP4gIZQ.png" /></figure><p>Simple TextView</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ada12096b20db4d23ebca941d7321215/href">https://medium.com/media/ada12096b20db4d23ebca941d7321215/href</a></iframe><h4>Customizing Chip</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/494/1*MY47UBhYzIAxiPxoVf4qOw.png" /></figure><p>Pretty much everything is self explanatory. There is one attribute we don’t see too much;<br><strong>elevationOverlayEnabled: </strong>This Boolean is being used to make elevation more noticeable for Dark Mode. For detailed explanation, <a href="https://material.io/develop/android/theming/dark/">check here.</a></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/56f3f38186e8767fc84602cf6ad5d380/href">https://medium.com/media/56f3f38186e8767fc84602cf6ad5d380/href</a></iframe><p>Chip background color;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/74248d0fc8d88f35521c87349b9ce812/href">https://medium.com/media/74248d0fc8d88f35521c87349b9ce812/href</a></iframe><p>Chip text color;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/76bd973576b39c0c8e51d21f1a96590c/href">https://medium.com/media/76bd973576b39c0c8e51d21f1a96590c/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/656/1*ORTqL7u-AFZu6W_Mo_rtjw.png" /></figure><p>For EditText border and <strong>CANCEL &amp; OK </strong>button colors, I had to override <strong>colorPrimary</strong> attribute. I believe this is kind of bug or something. Couldn’t find better solution for them. Also for EditText color, I had to override textColorPrimary attribute.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1a421096b889c1fc285f174c80e1d1d5/href">https://medium.com/media/1a421096b889c1fc285f174c80e1d1d5/href</a></iframe><p>For more information; <a href="https://material.io/components/time-pickers">https://material.io/components/time-pickers</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=61759abd034f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Publish a Single Repository — Multiple Libraries on JCenter using CI]]></title>
            <link>https://medium.com/hipo-labs/how-to-publish-a-single-repository-multiple-libraries-on-jcenter-using-ci-4ce1d03da972?source=rss-dff82d2ac66e------2</link>
            <guid isPermaLink="false">https://medium.com/p/4ce1d03da972</guid>
            <category><![CDATA[bitrise]]></category>
            <category><![CDATA[bintray]]></category>
            <category><![CDATA[android-libraries]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[open-source]]></category>
            <dc:creator><![CDATA[Mithat Sinan Sarı]]></dc:creator>
            <pubDate>Wed, 18 Nov 2020 17:03:17 GMT</pubDate>
            <atom:updated>2020-11-18T17:03:17.890Z</atom:updated>
            <content:encoded><![CDATA[<h3>How to Publish a Single Repository — Multiple Libraries on JCenter using CI</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cG8PNarL-pjQ1Ys_GpX7DQ.jpeg" /></figure><p><a href="https://github.com/Hipo/Macaron">Hipo/macaron</a></p><h4>How can we make publishing a library as easy as possible?</h4><p>That was the thought in our mind as we set out on this road. As Hipo, we decided to publish our Android libraries we use in our projects as open source and created Macaron. Macaron is a single repository that contains multiple open source library modules, like <a href="https://github.com/square/retrofit">Retrofit</a>. With this approach, libraries become easier to maintain and have a more organized structure. Also you can implement what you need. For example there are around 10 converters in <a href="https://github.com/square/retrofit/tree/master/retrofit-converters">Retrofit-converters</a> package, but we need only <a href="https://github.com/square/retrofit/tree/master/retrofit-converters/gson">Gson</a>. So we can implement Gson only, without implementing any unnecessary converter.</p><p>Since multiple developers are involved in development, we had to make the process easy to understand, easy to develop and easy to publish. We decided to use Bintray like we did before and we also decided to use CI for Macaron. We are currently using Bitrise for CI in Hipo. For Macaron, we decided to follow the same pattern.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yVow9WKQi5V1QHKpm3ES6w.png" /><figcaption>Macaron development flow</figcaption></figure><p>To publish the library — after everything is set up — only thing a developer needs to do is to add a tag and push it to GitHub. That’s it!</p><p>Last time, we talked about how to publish an open source library on JCenter. If you want to get more detail about JCenter and publishing single repo-single library, you can check the link below;</p><p><a href="https://blog.hipolabs.com/how-to-publish-library-on-jcenter-fdd60fa76b06">How to Publish an Open Source Android Library on JCenter</a></p><p>First we need to understand Bintray. What does Bintray need? We separate Bitray’s needs into 6 topics and created files for each of them.</p><ul><li>Bintray repository</li><li>Developer properties</li><li>Library properties</li><li>License properties</li><li>Local properties</li><li>Repository properties</li></ul><h4>Bintray Repository</h4><p>We are not able to push our packages if we don’t have a Bintray repository. It is pretty easy to create one;</p><p>Sign In -&gt; Click Add New Repository on Dashboard -&gt; Fill necessary fields.</p><blockquote>Make sure to choose Maven for type. We will publish on Maven in this example.</blockquote><h4>Developer Properties</h4><p>This file includes information about developer that developed the library module such as; Id, name, email and organisation.</p><pre>developerId=&#39;[BINTRAY_DEVELOPER_ID]&#39;<br>developerName=&#39;[DEVELOPER_NAME]&#39;<br>developerEmail=&#39;[DEVELOPER_EMAIL]&#39;<br>developerOrganisation=[DEVELOPER_ORGANISATION]</pre><h4>Library Properties</h4><p>Bintray use these properties to create library package.</p><pre>artifact=photocropping<br>libraryName=com.hipo.photocropping<br>libraryVersion=1.0.0<br>libraryDescription=Photo cropping library that uses PhotoView and has extensions for file provider</pre><p>Here you can see what they are used for;</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/914/1*n0AJqPWR7BydSaI0IYCwSA.jpeg" /></figure><p>Do not confuse the library name with implementation parameter. It will be described in <strong>Repository Properties.</strong></p><h4>License Properties</h4><p>Since we share all libraries with Apache-2.0, we didn’t separate <strong>license.properties</strong> file for each library module. These properties is being used to set library license;</p><pre>licenseName = &#39;The Apache Software License, Version 2.0&#39;<br>licenseUrl = &#39;http://www.apache.org/licenses/LICENSE-2.0.txt&#39;<br>allLicenses = [&quot;Apache-2.0&quot;]</pre><h4>Local Properties</h4><pre>bintrayUser=YOUR_BINTRAY_USERNAME<br>bintrayApiKey=YOUR_BINTRAY_API_KEY</pre><p>To publish without Bitrise, username and API key must be defined in <strong>local.properties </strong>to avoid from pushing it to GitHub.</p><h4>Repository Properties</h4><pre>bintrayRepoName=Macaron<br>publishedGroupId=com.hipo.macaron<br><br>macaronSiteUrl=https://github.com/Hipo/macaron<br>macaronGitUrl=https://github.com/Hipo/macaron.git</pre><p>Here we give our GitHub repository url and vcs url.</p><p><strong>bintrayRepoName</strong> has to be same as name of the repo you’ve created on Bintray.</p><p><strong>publishedGroupId </strong>determines what dependency parameter will be. For example we set our artifact as <strong>photocropping. </strong>Bintray will get this artifact name and combine with published group id and the result will be;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/37310620759a25ccf0b975394d6dd853/href">https://medium.com/media/37310620759a25ccf0b975394d6dd853/href</a></iframe><p>These are what Bintray needs.</p><h4>How do we create pom / aar packages and upload them to Bintray?</h4><p>First, we need a Gradle file! Let’s dive into our <strong>publish.gradle </strong>which is responsible of checking properties, installing pom&amp;aar packages and uploading them to Bintray.</p><h4>Defining property files</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/56e3e8ee3f41f49d44cc543dd43a1f57/href">https://medium.com/media/56e3e8ee3f41f49d44cc543dd43a1f57/href</a></iframe><p>Here we just read files that we’ve created before. license.properties, developer.properties, library.properties, macaron.properties…</p><blockquote>I want you to focus on if check on line 13. As we mentioned, we want Bitrise to publish our library. Since we keep our Bintray API key and username in <strong>local.properties</strong>, we can not push it to GitHub. So we have to check if we have <strong>local.properties </strong>on<strong> </strong>current environment or not. If we don’t have it, we use <strong>System.getenv() </strong>to get API key &amp; username from Bitrise Secret Envs.</blockquote><h4>Checking if everything is OK</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7917c564825aaf58544a7d587ffca7f8/href">https://medium.com/media/7917c564825aaf58544a7d587ffca7f8/href</a></iframe><p><strong>checkProperties </strong>reads every property in given files and throws exception if there is any missing one. If there isn’t a file called <strong>local.properties </strong>on current environment, it skips that file.</p><h4>Installing POM / AAR packages</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4d1c77b11fd631a3048bd13681b8a58a/href">https://medium.com/media/4d1c77b11fd631a3048bd13681b8a58a/href</a></iframe><p><strong>Install</strong> task is responsible of creating POM and AAR packages. It gets information it needs from files we’ve created before. We use <a href="https://github.com/dcendents/android-maven-gradle-plugin">Dcendents/Android-Maven-Gradle-Plugin</a> for this task.</p><h4>Uploading to Bintray</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cf2cb690bb53590a324fbd503ae4c512/href">https://medium.com/media/cf2cb690bb53590a324fbd503ae4c512/href</a></iframe><p>In <strong>bintray</strong> scope, we give what Bintray needs. Bintray repo, library name etc… They are also defined in files we’ve created before.</p><blockquote>Reason of reading properties from files is to use only one publish.gradle to publish all libraries.</blockquote><p>To make this happen, we get <strong>publish.gradle </strong>by adding the line below at the end of the build.gradle in library module.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dcf2ae0081f1679c935718570f9adf91/href">https://medium.com/media/dcf2ae0081f1679c935718570f9adf91/href</a></iframe><p>This line makes <strong>build.gradle</strong> read <strong>publish.gradle</strong> from our GitHub repository.</p><p>You can see full <strong>publish.gradle</strong> below:</p><p><a href="https://github.com/Hipo/macaron/blob/master/publish/publish.gradle">Hipo/macaron</a></p><p><strong>DO NOT FORGET TO ADD PLUGIN DEPENDENCIES INTO YOUR PROJECT build.gradle</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/61911b1ee503bfa12c46a69187069771/href">https://medium.com/media/61911b1ee503bfa12c46a69187069771/href</a></iframe><h4>It’s time to set up Bitrise.</h4><p>I won’t discuss how to add a new Bitrise app and what steps/workflows mean. Bitrise has pretty good documentation and UI.</p><ul><li><a href="https://devcenter.bitrise.io/getting-started/adding-a-new-app/">Bitrise Docs</a></li><li><a href="https://devcenter.bitrise.io/steps-and-workflows/steps-and-workflows-index/">Bitrise Docs</a></li></ul><p>Since we control everything from our GitHub repo, we decided to create a simple <strong>.sh</strong> script and let Bitrise run it for us, so we don’t have to update Bitrise workflows if we add new task to any of our gradle files.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ec58164a486f6176ec91108138d893af/href">https://medium.com/media/ec58164a486f6176ec91108138d893af/href</a></iframe><p>Just remove default steps and add a script step to your <strong>deploy </strong>workflow and give your script path to <strong>Script Content. </strong>Bitrise also let you write your script into there.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/967/1*AoeX3YRJcmgnHYs3nQwSAw.jpeg" /></figure><p>Let’s add our Bintray credentials to Bitrise. Go to <strong>Secrets </strong>and add your Bintray credentials as shown below. Keys have to match with <strong>System.getenv() </strong>parameters in <strong>publish.gradle</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*H5VH-KvcdbvE96aF7R2fjA.jpeg" /></figure><p>Since we use Gradle task in our bash script, we need to define Gradlew path in Env Vars. Make sure the key is <strong>GRADLEW_PATH;</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*t_ojciX4QhUV__-XhLI-PQ.jpeg" /></figure><p>Now, Bitrise knows how to check if there is any missing property, install packages and upload them to Bintray with our credentials. But, how does Bitrise know when to do these tasks? <strong>Triggers</strong> for the win!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3v5vG61btuCHo6QgU3r6sw.png" /></figure><p>We set our trigger tag as *.*.*-*, so whenever we push a tag which has the same pattern, our deploy workflow will run. By doing that we can choose which module to publish independently.</p><p>For example, there are 3 library modules but we want to publish only one of them. In this case, our tag will be <strong>1.0.0-alpha03-photocropping. </strong>Bash script will parse module name and will call necessary tasks, like; <strong>photocropping:install.</strong></p><p>Bitrise settings is done!</p><h4>Last but not least: Project Structure</h4><pre>Project<br>...<br> |<br> +-- library_module_1<br> |   |<br> |   |-- library.properties<br> |   \-- developer.properties<br> |<br> +-- library_module_2<br> |   |<br> |   |-- library.properties<br> |   \-- developer.properties<br> |<br> +-- publish<br> |   |<br> |   |-- bintray-publish-script.sh<br> |   |-- license.properties<br> |   |-- macaron.properties<br> |   \-- publish.gradle<br> |<br> +-- local.properties<br>...</pre><p>This is our project structure for Macaron.</p><ul><li>Since <strong>license.properties </strong>and <strong>macaron.properties</strong> are the same for all library modules, we don’t need to separate them. But each library module has to have their own <strong>library.properties</strong> and<strong> developer.properties</strong>.</li><li>As we mentioned before, they will all use same <strong>publish.gradle </strong>which was pushed to GitHub repository.</li><li>Bitrise will check ./publish/bintray-publish-script.sh to run bash script. So make sure that you give the right path for the script.</li></ul><p>That’s it!</p><p>Now you can create your open source project and give back to the community.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4ce1d03da972" width="1" height="1" alt=""><hr><p><a href="https://medium.com/hipo-labs/how-to-publish-a-single-repository-multiple-libraries-on-jcenter-using-ci-4ce1d03da972">How to Publish a Single Repository — Multiple Libraries on JCenter using CI</a> was originally published in <a href="https://medium.com/hipo-labs">Hipo</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Android Menu Summarized]]></title>
            <link>https://msinansari.medium.com/android-menu-summarized-ab59f8ea1517?source=rss-dff82d2ac66e------2</link>
            <guid isPermaLink="false">https://medium.com/p/ab59f8ea1517</guid>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[android-apps]]></category>
            <category><![CDATA[androiddev]]></category>
            <category><![CDATA[android-menu]]></category>
            <dc:creator><![CDATA[Mithat Sinan Sarı]]></dc:creator>
            <pubDate>Sun, 25 Oct 2020 15:23:44 GMT</pubDate>
            <atom:updated>2020-10-25T15:23:44.329Z</atom:updated>
            <content:encoded><![CDATA[<h3>Android Menus - Summarized</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bdrz1d7kkb7rUyhG5C2AsA.jpeg" /></figure><p>This blog will be a kind of reminder about Android menus. I will try to summarize as much as possible. I will focus on what they are and how we implement them.</p><p>Menus can contain group or a submenu.</p><h4>Group</h4><p>You can have checkbox or radio button behavior by settingcheckableBehavior of group item.</p><ul><li><strong>single</strong> Only one item from the group can be checked (radio buttons)</li><li><strong>all</strong> All items can be checked (checkboxes)</li><li><strong>none</strong> No items are checkable</li></ul><p>By using group , you can;</p><ul><li>Show or hide all items with <a href="https://developer.android.com/reference/android/view/Menu#setGroupVisible(int,%20boolean)">setGroupVisible()</a></li><li>Enable or disable all items with <a href="https://developer.android.com/reference/android/view/Menu#setGroupEnabled(int,%20boolean)">setGroupEnabled()</a></li><li>Specify whether all items are checkable with <a href="https://developer.android.com/reference/android/view/Menu#setGroupCheckable(int,%20boolean,%20boolean)">setGroupCheckable()</a></li></ul><p>You can see group example in<strong> Option Menu</strong> as <strong>Option 1 &amp; Option 2.</strong></p><h4>Submenu</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b5502be1c04fb495f72e3276706a3c2f/href">https://medium.com/media/b5502be1c04fb495f72e3276706a3c2f/href</a></iframe><p>You can create a submenu by adding menu item in your current menu.xml.</p><p>You can see submenu example in <strong>Option Menu</strong> as <strong>File — New &amp; Open</strong></p><p>There are <strong>3</strong> menu options;</p><ul><li>Option Menu</li><li>Contextual Menu | Floating Context Menu — Contextual Action Mode</li><li>Popup Menu</li></ul><h3>Option Menu</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/344/1*HcCxQd7GN3si67QH7S2zyQ.jpeg" /></figure><h4>Xml</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d4564f8e4aaff32fd801adf90aadfa77/href">https://medium.com/media/d4564f8e4aaff32fd801adf90aadfa77/href</a></iframe><h4>Code</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b3af506867b56c624bcd9de310e67d5b/href">https://medium.com/media/b3af506867b56c624bcd9de310e67d5b/href</a></iframe><h3>Contextual Menu</h3><h4>Floating Context Menu</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/688/1*zydW3kXs2HTmgdF3EhTVAA.png" /></figure><h4>Xml</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6037c20529f8cb2c10a04204985cf915/href">https://medium.com/media/6037c20529f8cb2c10a04204985cf915/href</a></iframe><h4>Code</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f2d1d536040a6b9a0c06d25870bd5c29/href">https://medium.com/media/f2d1d536040a6b9a0c06d25870bd5c29/href</a></iframe><p>We have to <strong>register</strong> our views which we want to show floating menu when they are long pressed.</p><h4>Contextual Action Mode</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/344/1*HN5VYxlOjVX2bhkZ84a_Vg.jpeg" /></figure><h4>Xml</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9458e65ebb9197731fb1467993baefa0/href">https://medium.com/media/9458e65ebb9197731fb1467993baefa0/href</a></iframe><h4>Code</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/562c4a1b47974d8b636e0f625f90465e/href">https://medium.com/media/562c4a1b47974d8b636e0f625f90465e/href</a></iframe><h3>Popup Menu</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/686/1*XNZS2T9ksMpgrl5niZhFJA.png" /></figure><h4>Xml</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/319cb2813bd1828eac2f7a7b3591211a/href">https://medium.com/media/319cb2813bd1828eac2f7a7b3591211a/href</a></iframe><h4>Code</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2a2779431865253912c5054d11054325/href">https://medium.com/media/2a2779431865253912c5054d11054325/href</a></iframe><p>To get more details;</p><p><a href="https://developer.android.com/guide/topics/ui/menus">Menus | Android Developers</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ab59f8ea1517" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Android JobScheduler]]></title>
            <link>https://medium.com/kodluyoruz/android-jobscheduler-5a78dc84f661?source=rss-dff82d2ac66e------2</link>
            <guid isPermaLink="false">https://medium.com/p/5a78dc84f661</guid>
            <category><![CDATA[android-apps]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[yazılımcı]]></category>
            <category><![CDATA[jobscheduler]]></category>
            <dc:creator><![CDATA[Mithat Sinan Sarı]]></dc:creator>
            <pubDate>Sat, 04 Jul 2020 10:03:48 GMT</pubDate>
            <atom:updated>2020-07-28T23:45:05.356Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-2D7Wv_i8qOu5fP7CLKthg.jpeg" /></figure><p>Belirli koşullar altında çalışmasını istediğimiz bir fonksiyon varsa ilk aklımıza gelen yapı JobScheduler olmalıdır. Örneğin uygulamamıza yeni bir versiyon çıktık ve bu versiyonun boyutu yüksek olduğu için sadece WiFi’a bağlı iken ve cihaz şarjda iken güncellenmesini istiyoruz. Bu ve benzeri durumlarda JobScheduler tam da aradığımız şey. Unutmamak gereklidir ki sadece API 21 ve üzerini desteklemektedir.</p><h4>Nelerden oluşur?</h4><ul><li><strong>JobInfo: </strong>JobScheduler ‘ ın hangi koşullar altında gerçekleşeceğini belirlediğimiz konfigürasyon nesnesi. JobScheduler ‘ın çalışması için en az bir koşul gereklidir.</li><li><strong>JobService: </strong>JobInfo içerisinde verdiğimi koşulların sağlanması durumunda çalışacak olan servis.</li></ul><p>JobService iki temel fonksiyondan oluşmakta;</p><ul><li><strong>onStartJob</strong>: İstediğimiz koşullar sağlandığında çağrılan fonksiyondur. MainThread üzerinde çalışır. Boolean bir değer döndürerek yapılması gereken işin ayrı bir threadde devam edip etmemesi gerektiğini belirleyebiliriz.<br> —<strong>true</strong>:Yapılan işin ayrı bir threadde devam ettiğini söylüyoruz. Bu durumda jobFinished() fonksiyonunu iş bittikten sonra bizim çağırmamız gerekiyor.<br> —<strong> false</strong>: Sistem, yapılan işin onStartJob() içinde bittiğini, yani ayrı bir thread’e geçmediğini bilir ve jobFinished() fonksiyonunu kendisi çağırır.</li><li><strong>onStopJob</strong>: Sistem, jobFinished() fonksiyonunun çağrılıp çağrılmadığına bakmaksızın yapılan işin durdurulması gerektiğine karar verdiğinde çağrılan fonksiyondur.</li></ul><p>Temel olarak yukarıda bahsettiğimiz yapılardan oluşmaktadır. Şimdi küçük bir örnek ile konuyu pekiştirelim;</p><p>Boş bir proje yarattıktan sonra, JobService extend eden bir sınıf oluşturalım. Bizim örneğimizde yapacağımız şey Toast göstermek olduğundan adını ToastJobService koydum.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cfd12146de10826bc90c1a661d45f084/href">https://medium.com/media/cfd12146de10826bc90c1a661d45f084/href</a></iframe><p>showToast() fonksiyonu farklı bir dosyada oluşturduğum basit bir extension. JobService aslında <a href="https://developer.android.com/guide/components/fundamentals#Components">Android’deki 4 temel bileşenden</a> biri olan Service ‘i extend ettiği için direkt olarak context ‘e erişebiliyoruz.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c1f2de3ff292d8c61e39d1250ea256ee/href">https://medium.com/media/c1f2de3ff292d8c61e39d1250ea256ee/href</a></iframe><p>Servis sınıfımızı oluşturduktan sonra, her serviste olduğu gibi JobService‘i de AndroidManifest.xml içerisine ekliyoruz;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d90ac80b771c167026d255bcb5a88a47/href">https://medium.com/media/d90ac80b771c167026d255bcb5a88a47/href</a></iframe><p>Sonrasında örnek uygulamamızın arayüzünü oluşturuyoruz. Buraya sadece ekran görüntüsü ve temel fonksiyonların açıklamalarını ekleyeceğim. Uygulamanın kodlarına en altta paylaşacağım GitHub repo’sundan erişebilirsiniz.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/240/1*i37_jIabV8kPw6dZb98Opg.jpeg" /></figure><p>Örnek uygulamada 3 farklı koşul belirleyebileceğiz. JobService’in bize sağladığı diğer koşulların neler olduğuna <a href="https://developer.android.com/reference/android/app/job/JobInfo.Builder">buradan</a> bakabilirsiniz.</p><p>Öncelikle bir JobScheduler ve ContentName nesnelerine ihtiyacımız var;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ed3ab3ace9159abae76ca6f3090a24c7/href">https://medium.com/media/ed3ab3ace9159abae76ca6f3090a24c7/href</a></iframe><p>Sonrasında, servisin hangi koşullar altında çalışacağını belirlediğimiz JobInfo nesnesi oluşturuyoruz;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/477076c6cfe66911cfe0f0b48e9d2e80/href">https://medium.com/media/477076c6cfe66911cfe0f0b48e9d2e80/href</a></iframe><ul><li><strong>JOB_ID</strong>: Her JobInfo nesnesinin eşsiz bir id’si olması gerekiyor. Bu id sayesinde dilediğimiz vakit, programlanmış bir Job’u iptal edebiliriz.</li><li><strong>setRequiredNetworkType</strong>: Gerekli olan ağ durumunu belirlediğimiz fonksiyon.</li></ul><blockquote>— <strong>NETWORK_TYPE_NONE</strong>: JobService, cihaz internete bağlı olsa da olmasa da çalışacaktır. Default değer budur.<br> —<strong> NETWORK_TYPE_ANY</strong>: JobService, cihaz internete bağlı olduğu durumda çalışacaktır (Hücresel ya da wifi olduğuna bakmaksızın).<br> —<strong> NETWORK_TYPE_UNMETERED</strong>: JobService, cihaz sadece Wifi’a bağlı olduğunda çalışacaktır.<br>—<strong> NETWORK_TYPE_CELLULAR</strong>: JobService, cihaz hücresel ağa bağlı olduğunda çalışacaktır.<br> —<strong> NETWORK_TYPE_NOT_ROAMING</strong>: JobService, <a href="https://tr.wikipedia.org/wiki/Dola%C5%9F%C4%B1m">Roaming</a> modu olmadığında çalışacaktır.</blockquote><ul><li><strong>setRequiresCharging</strong>: Job’un çalışması için cihazın şarjda olup olmama durumunu belirlediğimiz fonksiyon.</li><li><strong>setRequiresDeviceIdle</strong>: Keza bu da Job’ın çalışması için cihazın uyku modunda olup olmama durumunu belirlediğimiz fonksiyon.</li><li><strong>setOverrideDeadline</strong>: Parametre olarak Long tipinde bir değer alır. Eğer belirlediğimiz koşullar, parametre olarak gönderilen süre içinde gerçekleşmez ise sürenin sonunda yine de servisin başlatılıp başlatılmayacağını belirlediğimiz fonksiyon.</li></ul><p>Oluşturduğumuz JobInfo nesnesini JobScheduler nesnesinin schedule() fonksiyonuna parametre olarak veriyoruz ve Job’u programlamış oluyoruz.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/60b3f263700fe2f8dffec2674be174af/href">https://medium.com/media/60b3f263700fe2f8dffec2674be174af/href</a></iframe><p>Bu kadar basit! schedule() fonksiyonu çağrıldıktan sonra belirlenen koşullar sağlandığı takdirde ToastJobService içerisindeki onStartJob() fonksiyonu tetiklenecektir.</p><p>Iptal etmek istediğimiz durumda da iki farklı fonksiyon çağırabiliriz. Bunlar;</p><ul><li><strong>cancel(jobId: Int)</strong>: Sadece belirli JOB_ID’ye sahip olan Job’u iptal etmemizi sağlayan fonksiyon.</li><li><strong>cancelAll()</strong>: JobScheduler nesnesine bağlı olan tüm Job’ları tek seferde iptal etmemizi sağlayan fonksiyon.</li></ul><p><a href="https://github.com/mitsinsar/JobSchedulerDemo">mitsinsar/JobSchedulerDemo</a></p><p><a href="https://codelabs.developers.google.com/codelabs/android-training-job-scheduler/index.html?index=..%2F..%2Fandroid-training#0">Codelabs</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5a78dc84f661" width="1" height="1" alt=""><hr><p><a href="https://medium.com/kodluyoruz/android-jobscheduler-5a78dc84f661">Android JobScheduler</a> was originally published in <a href="https://medium.com/kodluyoruz">Kodluyoruz</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Multiple ViewType, Multiple GridSize, Multiple Item Selection in RecyclerView]]></title>
            <link>https://msinansari.medium.com/multiple-viewtype-multiple-gridsize-multiple-item-selection-in-recyclerview-4b076d0ec79f?source=rss-dff82d2ac66e------2</link>
            <guid isPermaLink="false">https://medium.com/p/4b076d0ec79f</guid>
            <category><![CDATA[grid-layout]]></category>
            <category><![CDATA[android-recyclerview]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[recyclerview-selection]]></category>
            <category><![CDATA[recyclerview]]></category>
            <dc:creator><![CDATA[Mithat Sinan Sarı]]></dc:creator>
            <pubDate>Thu, 23 Jan 2020 08:21:52 GMT</pubDate>
            <atom:updated>2020-01-23T08:21:52.505Z</atom:updated>
            <content:encoded><![CDATA[<p>If you think title is a bit long, you should see my adapter class…</p><p><strong>TL:DR</strong>; You can check GitHub repository that I shared at the end of the story.</p><p>First let me show you what we are going to have at the end of this story;</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/156/1*t5Lqc2wxc84e8QFKPqqlgw.gif" /></figure><p>There are;</p><ul><li>Different view types</li><li>Different grid sizes</li><li>Multiple item selection</li></ul><p>in one recycler view. Yeap…<br>Not three, not two… <br>Just one recycler view!</p><h3>So let’s get to work!</h3><h4>List Items</h4><p>First of all we need a base class for list items for recycler view adapter. Which I called ListItem;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/316ccb826f9282faf35c282b496c287b/href">https://medium.com/media/316ccb826f9282faf35c282b496c287b/href</a></iframe><p>It is <strong>sealed class. </strong>So we will be able to use subclasses like enum classes.<br>In inner enum class, I defined all view types that I am going to use in adapter to create different ViewHolders. And of course every list item will have a type. So <strong>getType()</strong> will be useful here.</p><p>Some of these items will be selectable (Char and Digit). So would be better if we collect them under SelectableListItem which extends ListItem;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3efd61be0b3c0f2b09f7529093d858aa/href">https://medium.com/media/3efd61be0b3c0f2b09f7529093d858aa/href</a></iframe><p>Now let’s create our list items;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/528a8176b58960ecfbdc0277265b0087/href">https://medium.com/media/528a8176b58960ecfbdc0277265b0087/href</a></iframe><p>As you can see, <strong>TitleListItem</strong> extends ListItem but other items which are going to be selectable extend <strong>SelectableListItem.</strong></p><h4>View Holders</h4><p>Now it is time to create view holders. We are going to create three view holders and two of them will extend SelectableViewHolder. Why? Because they will be selectable;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b3060b637ced89a97684d452b23b9a06/href">https://medium.com/media/b3060b637ced89a97684d452b23b9a06/href</a></iframe><p>We are going to use <strong>bind(isSelected: Boolean)</strong> function to be able to change background color without updating whole <strong>itemView</strong>. aka Payload.</p><p>This is our DigitViewHolder, it is going to hold simple TextView and ConstraintLayout as a root layout. As you can see we overrided <strong>bind </strong>function to handle item selection.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a936b0aa5422cc0b3d1645117ef9491f/href">https://medium.com/media/a936b0aa5422cc0b3d1645117ef9491f/href</a></iframe><p>I will not copy-paste CharViewHolder because it is almost the same as DigitViewHolder. If you want to see what is inside of it, you can check GitHub repository that I am going to share at the end of the page.</p><p>TitleViewHolder is similar to DigitViewHolder, but it extends RecyclerView.ViewHolder. Rest of it is the same.</p><h4>Adapter</h4><p>I like to create new list for adapter to handle item changes in adapter. If you pass your list in the constructor and change something in the list outside of adapter, you should also call notify functions outside of adapter.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0941598e8ffc2899ab7f35985a6af721/href">https://medium.com/media/0941598e8ffc2899ab7f35985a6af721/href</a></iframe><p>Simple mutable list that holds <strong>ListItem</strong> and its setter function to set items.</p><p>Let’s remove TODO’s in overrided functions;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2392b243ea4537ae57bbd4ddd93a94e7/href">https://medium.com/media/2392b243ea4537ae57bbd4ddd93a94e7/href</a></iframe><ul><li><strong>getItemCount()</strong> : Item count of list that we use in adapter.</li><li><strong>onCreateViewHolder() </strong>: This is where we create our view holder items and set their click actions. As you can see we are using viewType param. Where does it come from?</li><li><strong>onBindViewHolder()</strong> : In this method we update view holder’s item based on list item.</li></ul><p>Here… viewType param comes from here. Since item list holds ListItem, that means we can simply call items[position].getType() and return ListItem.Type’s ordinal.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e61ca068759b5b2600262d6c0573e834/href">https://medium.com/media/e61ca068759b5b2600262d6c0573e834/href</a></iframe><h4>Different Grid Sizes</h4><p>How do we achieve different grid sizes in one recycler view. There is a simple object called “<strong>mSpanSizeLookup</strong>” in GridLayoutManager (GLM). GLM updates every item based on it’s span size. If you use GridLayoutManager(context, SPAN_SIZE), there will be SPAN_SIZE items in every row. So we should set new “<strong>mSpanSizeLookup</strong>” for our GLM to achieve different item count in different rows.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/98ec2f80c0dbd98ec57ddb817f0a10ad/href">https://medium.com/media/98ec2f80c0dbd98ec57ddb817f0a10ad/href</a></iframe><p>in <strong>onAttachedToRecyclerView </strong>we can set new SpanSizeLookup for our GLM.</p><p>There are two important rules</p><ul><li>LayoutManager must be GridLayoutManager</li><li>LayoutManager must be set before Adapter</li></ul><p>First one is obvious, let me explain second one; <strong>onAttachedToRecyclerView</strong> is called when we set recycler view’s adapter. If we set adapter first, <strong>recyclerView.layoutManager as GridLayoutManager </strong>will throw NullPointerException, because we haven’t set layout manager yet. That’s why we need to set layout manager first. As an example;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/80d7b5badc13b9c9556cffcc5e67062d/href">https://medium.com/media/80d7b5badc13b9c9556cffcc5e67062d/href</a></iframe><p>Let’s go back to<strong> getSpanSizeLookup()</strong> function. It extends <strong>GridLayoutManager.SpanSizeLookup </strong>because type of our “<strong>mSpanSizeLookup</strong>” is <strong>SpanSizeLookup. *insert you don’t say meme*</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1d50cf619404cf7ec64b3a09b520413b/href">https://medium.com/media/1d50cf619404cf7ec64b3a09b520413b/href</a></iframe><p>The Int we return is the span size of a single view. In this example, I simply checked the view type and set span size based on view type. DEFAULT_SPAN_SIZE is 1 by the way. Default value of GLM’s SpanSizeLookup. SPAN_SIZE part is the overriden part. <br>In this example, SPAN_SIZE is 4 and DIGIT_SPAN_SIZE is 2. You can set however you want to.</p><h4>Handling Selections</h4><p>I’ve created two variables to hold selected item’s position. So in activity/fragment, we can get selected item.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/efbe13b429367e90de37ea0c604d34ee/href">https://medium.com/media/efbe13b429367e90de37ea0c604d34ee/href</a></iframe><p>Thanks to my colleagues, I’ve learnt that there is something called <strong>Delegates.observable. </strong>It is like overriding its setter. <br>*<strong>insert same but different meme</strong>*</p><blockquote>If you want to learn more about Delegates.observable, click <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.properties/-delegates/observable.html">here</a>.</blockquote><p>When value of variable changes, observable function is being triggered and we call handleItemSelection;</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8f93306c1c22e4718da00e1cfed53b09/href">https://medium.com/media/8f93306c1c22e4718da00e1cfed53b09/href</a></iframe><p>It deselects old selected item and selects new item. We need to check if oldValue is RecyclerView.NO_POSITION or not. Otherwise when we try to deselect old item, we will get IndexOutOfBoundException. Because we set RecyclerView.NO_POSITION as first value of variables. <br>Remember<strong> Delegates.observable&lt;Int&gt;(RecyclerView.NO_POSITION)<br></strong>You will understand why we will get IndexOutOfBoundException when you see <strong>setSelectedItem</strong> function.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f7cb012af6713dea65a8640e06e93ca5/href">https://medium.com/media/f7cb012af6713dea65a8640e06e93ca5/href</a></iframe><p>If we don’t check if oldValue is NO_POSITION or not, items[NO_POSITION] will cause IndexOutOfBoundException. Because RecyclerView.NO_POSITION is -1.</p><p>In this function, we set selection status of item then trigger <strong>notifyItemChanged</strong> with position and payload.</p><p>And lastly we update our item in <strong>onBindViewHolder;</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b4b91f35096e6ba9155576d3a45c08fb/href">https://medium.com/media/b4b91f35096e6ba9155576d3a45c08fb/href</a></iframe><p>We overrided onBindViewHolder because we want to change only background of the item. If you call <strong>notifyItemChanged</strong> without payload, it updates whole item and it can cause undesired blink effect.</p><p>In this blog, you can see what I am talking about;</p><p><a href="https://medium.com/livefront/recyclerview-trick-selectively-bind-viewholders-with-payloads-4b28e3d2cce8">RecyclerView Trick: Selectively bind ViewHolders with Payloads</a></p><p>If you combine all of the stuff I explained above, you will have what you see in the first gif. Or you can simply check this GitHub repository to see source codes;</p><p><a href="https://github.com/mitsinsar/medium_app_1">mitsinsar/medium_app_1</a></p><p>Thanks y’all!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4b076d0ec79f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Android App Bundles & Local Testing]]></title>
            <link>https://medium.com/hipo-labs/android-app-bundle-local-testing-b5a48a7bb4f6?source=rss-dff82d2ac66e------2</link>
            <guid isPermaLink="false">https://medium.com/p/b5a48a7bb4f6</guid>
            <category><![CDATA[android-app-bundle]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[app-bundle]]></category>
            <dc:creator><![CDATA[Mithat Sinan Sarı]]></dc:creator>
            <pubDate>Thu, 09 Jan 2020 12:41:45 GMT</pubDate>
            <atom:updated>2020-01-24T18:10:20.153Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/900/1*WtJHJ65ZzPhxp2ea-nkZmQ.jpeg" /></figure><p>Apps binaries that we deliver to the Play Store contain tons of libraries and resources. We can reduce the amount of unnecessary data to an extent with proguard but still we couldn’t generate device configuration specific APKs. You might think this has minimal impact, but there are lots of configurations:</p><ul><li>Processor types (arm based armeabi-v7a, arm64-v8a or intel based x86, x86_64…)</li><li>Sreen resolutions (hdpi, xhdpi, xxhdpi…)</li></ul><p>To be able to support all of these combinations, we used to include everything in the same package and deliver that to all users. This is where the new app bundles come in!</p><p>With app bundles, users get only what they need based on the device they are installing the app on, instead of having all possible combinations. If the device has a 32-bit processor, app bundle takes compatible APK from the package. Then looks at the screen size of the same device and takes the matching resources for that specific screen resolution. It does the same thing for locale and SDK version, prepares a new APK and makes it available for the user. This results in dramatically reduced download sizes!</p><p>As an example, we’ve recently generated an AAB for a project we have been working on, and there were 95 little APKs in it with a total size of 38 MB. My device needs only four of them, which is a total of 27 MB. The difference is huge: 11 MB! If we consider that my device’s screen resolution is xxhdpi, difference will be even more when target device’s resolution is lower than xxhdpi.</p><h3>Where to start?</h3><p>Assuming you are now sold on the benefits of app bundles, let’s see how we can switch to using them! Generating an app bundle is very similar to how you used to generate APKs:<br>Build -&gt; Generate Signed Bundle/APK… And this screen comes up:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/673/1*LP7C1JyzocS83QpikZkaow.png" /></figure><p>Choose App Bundle. And click next, fill the required fields:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/714/1*7OIgHqfXxg1lA0WhAMvtPw.png" /></figure><p>You may want to check “Remember passwords”.</p><p>There is another checkbox about encrypted key. It creates .pepk file for Play Store. If you decide to use App Signing, you are going to need that .pepk file.</p><blockquote>For extra information about App Signing, you can check <a href="https://support.google.com/googleplay/android-developer/answer/7384423">here</a>.</blockquote><p>As last step, we will choose destination folder and build variants, then click finish.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/671/1*QjpJLyikcKTXtDPcWOPj1Q.png" /></figure><p>Now you have a signed app bundle ready for Play Store! 🎉</p><p>In the destination folder, there should be an .aab file. It is your bundle file. Now you can upload and publish your bundle file on Play Store.</p><h3>How to test?</h3><p>Another thing you might want to do is to be able to locally test the app bundle and its outputs. Here is what you will need to do be able to do this:</p><ul><li>An android test device</li><li>Bundletool</li><li>Unzipping tools like Winrar or The Unarchiver (optional)</li></ul><h4>Let’s start!</h4><p>First we need to download Bundletool. You can find the latest version on <a href="https://github.com/google/bundletool/releases">Google Bundletool Github repo</a>.</p><p>After downloading bundletool, we are going to use the terminal. Hacker time!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*RoV-RJEsI6CcIN9HgEVMzw.gif" /></figure><p><em>Since I don’t use any helper tools like Homebrew, I will explain everything on a vanilla terminal setup.</em></p><p>First, we need to build an .apks file. .apks file is an <strong><em>APK set archive</em></strong><em> </em>that contains all the APKs generated by bundletool. To build that file, we are going to use the <strong>build-apks </strong>command and some parameters.</p><p>These are the parameters we are going to use:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d366b4e3c0a5a862cb6d9ce9beb4d52a/href">https://medium.com/media/d366b4e3c0a5a862cb6d9ce9beb4d52a/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e0590e83f4f8fe78df3a10bdd7079a6b/href">https://medium.com/media/e0590e83f4f8fe78df3a10bdd7079a6b/href</a></iframe><p>If you dont give keystore parameters like keystore file or pass, APKs will be signed with the debug keystore. Let’s give keystore parameters to generate signed APKs:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a044f92f9981911ddaf57b4e4707a87b/href">https://medium.com/media/a044f92f9981911ddaf57b4e4707a87b/href</a></iframe><p>Lets combine all of them:</p><pre>java -jar ~/Downloads/bundletool-all-0.12.0.jar build-apks --bundle=~/StudioProjects/MyApp/app/app-prod-release.aab --output=out_bundle_archive_set.apks <br>--ks=~/StudioProjects/MyApp/keystore.jks <br>--ks-pass=pass:mypassword123 <br>--ks-key-alias=MyKeyAlias <br>--key-pass=pass:mypassword123</pre><p>This single command will create an .apks file in our root folder.</p><p>There was an issue about installing device specific APKs with <strong><em>— connected-device </em></strong>command. It gives <strong><em>UnknownFlagsException. </em></strong>I believe they will fix this soon. If you don’t use <strong><em>— connected-device</em></strong> flag, bundletool generates APKs for all device configurations your app supports. This is basically like the legacy APK.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OujGJtWPZ3ms093k0DHd1w.png" /></figure><p>That’s why we are going to generate our device specific APKs and install them manually.</p><h4>Generate device specific APKs</h4><p>First we need to create json file that contains our device’s configs:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/664aea803630c20e2441dddca4cb1427/href">https://medium.com/media/664aea803630c20e2441dddca4cb1427/href</a></iframe><p>This is my device’s config json file. You should create your own test device’s config json. After creating json file, we are able to create device specific APKs. Now let’s call command below:</p><pre>java -jar ~/Downloads/bundletool-all-0.12.0.jar extract-apks --apks=~/out_bundle_archive_set.apks <br>--output-dir=~/apks/huawei <br>--device-spec=~/huawei_spec.json</pre><p>Now we have device specific APKs. Don’t try to install them individually, doesn’t work (I’ve tried). To install that APK we need to call:</p><pre>./adb install-multiple <br>~apks/huawei/base-tr.apk<br>~apks/huawei/base-arm64_v8a_2.apk<br>~apks/huawei/base-master_2.apk<br>~apks/huawei/base-xxhdpi.apk</pre><p>Those are my device specific APKs and the folder is where I extracted my APKs. Yours will be different, so don’t forget to change them!</p><blockquote>If you get <strong>adb command not found</strong> error, make sure that you are in platform-tools folder. <br>It is usually in ~/Libraries/Android/sdk/platform-tools/adb (Mac)</blockquote><p>That’s it! Now you can test your app on your device!</p><h4>Optional part</h4><p>To see all the APKs that bundle generates, go to the folder where you extract your .apks file and extract it with unzipping tool. You will see <strong>splits</strong> folder and <strong>toc.pb</strong> file. <strong>splits </strong>folder contains all the APKs. You can try different packages, like re-install your app by choosing different language package etc.</p><p>Keep an eye on posts from Hipo by <a href="http://eepurl.com/OAHCz">subscribing to our newsletter</a> and <a href="https://twitter.com/hipolabs">following us on Twitter</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b5a48a7bb4f6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/hipo-labs/android-app-bundle-local-testing-b5a48a7bb4f6">Android App Bundles &amp; Local Testing</a> was originally published in <a href="https://medium.com/hipo-labs">Hipo</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Automated Text Input Formatting on Android with MaskedEditText]]></title>
            <link>https://medium.com/hipo-labs/text-masker-for-android-maskededittext-1951c10cdee3?source=rss-dff82d2ac66e------2</link>
            <guid isPermaLink="false">https://medium.com/p/1951c10cdee3</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[android-libraries]]></category>
            <category><![CDATA[edittext]]></category>
            <dc:creator><![CDATA[Mithat Sinan Sarı]]></dc:creator>
            <pubDate>Mon, 06 Jan 2020 09:50:28 GMT</pubDate>
            <atom:updated>2020-01-10T14:56:40.237Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eBrEh8un6EziFmpzDbgOvQ.jpeg" /></figure><p>On one of our recent projects, we were faced with one of those seemingly simple but actually hard coding problems: Building a text input field that can automatically format and validate the input while the user is typing. After some trial and error, we ended up building an open source library to solve this issue: <a href="https://github.com/Hipo/hipo-maskededittext-android">MaskedEditText</a>.</p><p>Seasoned Android developers might be asking: Why do we even need a new library when there is a class called PhoneNumberFormattingTextWatcher? Our answer would be: Have you ever used it? Implementation is easy:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9be66782574447c00ec27596f5566580/href">https://medium.com/media/9be66782574447c00ec27596f5566580/href</a></iframe><p>But let’s see how this works in a real world scenario:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/320/1*vQQglJB7VJezE7ZZNyTVIA.gif" /></figure><p>Needless to say, that’s not good UX, and no self-respecting developer can ship with a solution like that. Default solution places parentheses when you don’t expect it and goes completely bonkers if you type more numbers than it expects. That’s why we need an extra library and this is how MaskedEditText came to exist.</p><h4>How does it work?</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/320/1*ks17QTGZpDIt4hoUh1O_UQ.gif" /></figure><p>It works as expected, no magic here, just good UX.</p><p>It has four options:</p><ul><li>Phone Number (###) ###-####</li><li>Date ## / ## / ####</li><li>SSN Number ###-##-####</li><li>Custom (However you want it to be)</li></ul><h4>How to use it?</h4><p>Define an edit text field in XML and its maskType. There is no default maskType, so it will give you a runtime error if maskType is not selected.</p><pre>Caused by: java.lang.Exception: MaskedEditText: maskType must be selected</pre><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/aceb6449c5afc5bd402ae03299f06ca2/href">https://medium.com/media/aceb6449c5afc5bd402ae03299f06ca2/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/193f69610dfcbb527d4779d5b06a9b7d/href">https://medium.com/media/193f69610dfcbb527d4779d5b06a9b7d/href</a></iframe><p>That’s it!</p><p>To use custom pattern you need to follow some rules;</p><ul><li>maskPattern and returnPattern must be defined</li><li>Pound (#) count for maskPattern and returnPattern must be the same</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ff40628c7c06788ac618a7208160ef03/href">https://medium.com/media/ff40628c7c06788ac618a7208160ef03/href</a></iframe><p>Otherwise it will throw runtime exception with explanation.</p><h4>Under the hood</h4><p>It’s just an edit text field that doesn’t trigger itself when text is set by masker manually. And this edit text field uses a class called <strong>Masker </strong>to handle text changes.<strong> </strong>It accepts <strong>Mask </strong>(which is type of mask; date, ssn, etc.) and onTextMaskedListener. This listener is triggered when formatting is done.</p><p>Since we use kotlin, we can simply use it as below:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/80cb5e62f1506b556dee38a1db21d056/href">https://medium.com/media/80cb5e62f1506b556dee38a1db21d056/href</a></iframe><p>In MaskedEditText, when onTextChanged is triggered, we also trigger masker’s onTextChanged function. Then it triggers onTextMaskedListener.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7c3221d2a955de00744b1b3f788b97f6/href">https://medium.com/media/7c3221d2a955de00744b1b3f788b97f6/href</a></iframe><p>Every mask type extends abstract <strong>Mask </strong>class.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1721c77bf632509f8d51214ab5998a0e/href">https://medium.com/media/1721c77bf632509f8d51214ab5998a0e/href</a></iframe><p>As you can see, there is a field called <strong>returnPattern. </strong>Yes you can define your return pattern.</p><p>Phone and SSN returns only digits, date returns a string with YYYY-MM-DD pattern. You need to use fun getParsedText(maskedText: String): String? function to get parsed text. Why is return type nullable? To understand if return pattern and mask pattern matches or not. If user doesn’t fill the expected input, it returns null.</p><p>You can find whole project at the link below and all contributions are welcome!</p><p><a href="https://github.com/Hipo/hipo-maskededittext-android">Hipo/hipo-maskededittext-android</a></p><p>It is also in jcenter which means you can simply add it to your project as so:</p><p>implementation &#39;com.hipo.maskededittext:maskededittext:1.1.0&#39;</p><p>Thanks!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*Mcws7l5T-Rh88gXAymBRzA.gif" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1951c10cdee3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/hipo-labs/text-masker-for-android-maskededittext-1951c10cdee3">Automated Text Input Formatting on Android with MaskedEditText</a> was originally published in <a href="https://medium.com/hipo-labs">Hipo</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>