Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
aalhamali committed Aug 30, 2018
0 parents commit 364a71a
Show file tree
Hide file tree
Showing 71 changed files with 3,460 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.DS_Store
.dart_tool/

.packages
.pub/

build/
ios/.generated/
ios/Flutter/Generated.xcconfig
ios/Runner/GeneratedPluginRegistrant.*

.idea
pubspec.lock
flutter_typeahead
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 0.1.0 - TODO: Add release date.

* TODO: Describe initial release.
25 changes: 25 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
BSD 2-Clause License

Copyright (c) 2018, AbdulRahmanAlHamali
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
211 changes: 211 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# Flutter TypeAhead
A TypeAhead widget for Flutter, where you can show suggestions to
users as they type

<img src="https://raw.githubusercontent.com/AbdulRahmanAlHamali/flutter_typeahead/master/flutter_typeahead.gif">

## Features
* Shows suggestions in an overlay that floats on top of other widgets
* Allows you to specify what the suggestions will look like through a
builder function
* Allows you to specify what happens when the user taps a suggestion
* Accepts all the parameters that traditional `TextFields` accept, like
decoration, custom `TextEditingController`, text styling, etc.
* Provides two versions, a normal version and a [FormField](https://docs.flutter.io/flutter/widgets/FormField-class.html)
version that accepts validation, submitting, etc.
* Provides high customizability; you can customize the suggestion box decoration,
the loading bar, the animation, the debounce duration, etc.

## Installation
See the [installation instructions on pub](https://pub.dartlang.org/packages/flutter_typeahead#-installing-tab-).

## Usage examples
After you import the package:
```dart
import 'package:flutter_typeahead/flutter_typeahead.dart';
```

You can then use it as follows:
```dart
TypeAheadField(
autofocus: true,
style: DefaultTextStyle.of(context).style.copyWith(
fontStyle: FontStyle.italic
),
decoration: InputDecoration(
border: OutlineInputBorder()
),
suggestionsCallback: (pattern) async {
return BackendService.getSuggestions(pattern);
},
itemBuilder: (context, suggestion) {
return ListTile(
leading: Icon(Icons.shopping_cart),
title: Text(suggestion['name']),
subtitle: Text('\$${suggestion['price']}'),
);
},
onSuggestionSelected: (suggestion) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ProductPage(product: suggestion)
));
},
)
```
In the code above, the `autfocus`, `style` and `decoration` are the same as
those of `TextField`, and are not mandatory.

The `suggestionCallback` is called with the search string that the user
types, and is expected to return a `List` of data, either synchronously or
asynchronously. In this example, we call some function called
`BackendService.getSuggestions` and provide it with the search pattern

The `itemBuilder` is called to build a widget for each suggestion.
In this example, we build a simple `ListTile` that shows the name and the
price of the item. Please note that you shouldn't provide an `onTap`
callback here. The TypeAhead widget takes care of that.

The `onSuggestionSelected` is a callback that provides us with the
suggestion that the user tapped. In this example, when the user taps a
suggestion, we navigate to a page that shows us the information of the
tapped product.

Here's another example, where we use the TypeAheadFormField inside a `Form`:
```dart
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final TextEditingController _typeAheadController = TextEditingController();
String _selectedCity;
...
..
.
Form(
key: this._formKey,
child: Padding(
padding: EdgeInsets.all(32.0),
child: Column(
children: <Widget>[
Text(
'What is your favorite city?'
),
TypeAheadFormField(
controller: this._typeAheadController,
decoration: InputDecoration(
labelText: 'City'
),
suggestionsCallback: (pattern) {
return CitiesService.getSuggestions(pattern);
},
itemBuilder: (context, suggestion) {
return ListTile(
title: Text(suggestion),
);
},
transitionBuilder: (context, suggestionsBox, controller) {
return suggestionsBox;
},
onSuggestionSelected: (suggestion) {
this._typeAheadController.text = suggestion;
},
validator: (value) {
if (value.isEmpty) {
return 'Please select a city';
}
},
onSaved: (value) => this._selectedCity = value,
),
SizedBox(height: 10.0,),
RaisedButton(
child: Text('Submit'),
onPressed: () {
if (this._formKey.currentState.validate()) {
this._formKey.currentState.save();
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Your Favorite City is ${this._selectedCity}')
));
}
},
)
],
),
),
)
```
Here, we are assigning to the `controller` property a
`TextEditingController` that we called `_typeAheadController`. We use this
controller in the `onSuggestionSelected` callback to set the value of the
`TextField` to the selected suggestion.

The `validator` callback can be used like any `FormField.validator` function.
In our example, it checks whether a value has been entered, and displays an
error message if not. The `onSaved` callback is used to save the value of
the field to the `_selectedCity` member variable

The `transitionBuilder` allows us to customize the animation of the
suggestion box. In this example, we are just returning the suggestionBox
immediately, meaning that we don't want any animation.

## Customizations
TypeAhead widgets consist of a `TextField` and a suggestion box that shows
as the user types. Both are highly customizable

### Customizing the TextField
You can customize the field with all the usual customizations available for
`TextField` in Flutter. Examples include: decoration, style, controller,
focusNode, autofocus, enabled, etc.

### Customizing the Suggestions Box
TypeAhead provides default configurations for the suggestions box. You can,
however, override most of them.

#### Customizing the loader, the error and the "no items found" message
You can use the loadingBuilder, errorBuilder and noItemsFoundBuilder to
customize their corresponding widgets. For example, to show a custom error
widget:
```dart
errorBuilder: (BuildContext context, Object error) =>
Text(
'$error',
style: TextStyle(
color: Theme.of(context).errorColor
)
)
```
#### Customizing the animation
You can customize the suggestion box animation through 3 parameters: the
`animationDuration`, the `animationStart`, and the `transitionBuilder`. The
`animationDuration` specifies how long the animation should take, while the
`animationStart` specified what point (between 0.0 and 1.0) the animation
should start from. The `transitionBuilder` accepts the `suggestionsBox` and
`animationController` as parameters, and should return a widget that uses
the `animationController` to animate the display of the `suggestionsBox`.
For example:
```dart
transitionBuilder: (context, suggestionsBox, animationController) =>
FadeTransition(
child: suggestionsBox,
opacity: CurvedAnimation(
parent: animationController,
curve: Curves.fastOutSlowIn
),
)
```
In order to fully remove the animation, this callback should simply return
the `suggestionsBox`. This callback could also be used to wrap the
`suggestionsBox` with any desired widgets, not necessarily for animation.

#### Customizing the debounce duration
The suggestion box does not fire for each character the user types. Instead,
we wait until the user is idle for a duration of time, and then we call the
`suggestionsCallback`. The duration defaults to 300 milliseconds, but can be
configured using the `debounceDuration` parameter

#### Customizing the decoration of the suggestions box
You can also customize the decoration of the suggestions box. For example,
to give it a blue border, you can write:
```dart
decoration: BoxDecoration(
border: Border.all(
color: Colors.blue
)
)
```
9 changes: 9 additions & 0 deletions example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.DS_Store
.dart_tool/

.packages
.pub/

build/

.flutter-plugins
8 changes: 8 additions & 0 deletions example/.metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
revision: c7ea3ca377e909469c68f2ab878a5bc53d3cf66b
channel: beta
8 changes: 8 additions & 0 deletions example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# example

A usage example of flutter_typeahead

## Getting Started

For help getting started with Flutter, view our online
[documentation](https://flutter.io/).
10 changes: 10 additions & 0 deletions example/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
*.iml
*.class
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
GeneratedPluginRegistrant.java
51 changes: 51 additions & 0 deletions example/android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 27

lintOptions {
disable 'InvalidPackage'
}

defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "aalhamali.example"
minSdkVersion 16
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}

flutter {
source '../..'
}

dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}
39 changes: 39 additions & 0 deletions example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="aalhamali.example">

<!-- The INTERNET permission is required for development. Specifically,
flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>

<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="example"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
Loading

0 comments on commit 364a71a

Please sign in to comment.